@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/cli.js CHANGED
@@ -1289,22 +1289,21 @@ var init_schema2 = __esm({
1289
1289
  }
1290
1290
  });
1291
1291
 
1292
- // src/analyzer/json-utils.ts
1293
- function tryParseJson(text) {
1294
- try {
1295
- return JSON.parse(text);
1296
- } catch {
1297
- return null;
1298
- }
1299
- }
1300
- var init_json_utils = __esm({
1301
- "src/analyzer/json-utils.ts"() {
1302
- "use strict";
1303
- }
1304
- });
1305
-
1306
1292
  // src/analyzer/tsdoc-parser.ts
1307
1293
  import * as ts from "typescript";
1294
+ import {
1295
+ checkSyntheticTagApplication,
1296
+ extractPathTarget as extractSharedPathTarget,
1297
+ getTagDefinition,
1298
+ hasTypeSemanticCapability,
1299
+ parseConstraintTagValue,
1300
+ parseDefaultValueTagValue,
1301
+ resolveDeclarationPlacement,
1302
+ resolvePathTargetType,
1303
+ sliceCommentSpan,
1304
+ parseCommentBlock,
1305
+ parseTagSyntax
1306
+ } from "@formspec/analysis";
1308
1307
  import {
1309
1308
  TSDocParser,
1310
1309
  TSDocConfiguration,
@@ -1350,6 +1349,291 @@ function createFormSpecTSDocConfig(extensionTagNames = []) {
1350
1349
  }
1351
1350
  return config;
1352
1351
  }
1352
+ function sharedCommentSyntaxOptions(options, offset) {
1353
+ const extensions = options?.extensionRegistry?.extensions;
1354
+ return {
1355
+ ...offset !== void 0 ? { offset } : {},
1356
+ ...extensions !== void 0 ? { extensions } : {}
1357
+ };
1358
+ }
1359
+ function sharedTagValueOptions(options) {
1360
+ return {
1361
+ ...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
1362
+ ...options?.fieldType !== void 0 ? { fieldType: options.fieldType } : {}
1363
+ };
1364
+ }
1365
+ function buildSupportingDeclarations(sourceFile) {
1366
+ return sourceFile.statements.filter(
1367
+ (statement) => !ts.isImportDeclaration(statement) && !ts.isImportEqualsDeclaration(statement) && !(ts.isExportDeclaration(statement) && statement.moduleSpecifier !== void 0)
1368
+ ).map((statement) => statement.getText(sourceFile));
1369
+ }
1370
+ function renderSyntheticArgumentExpression(valueKind, argumentText) {
1371
+ const trimmed = argumentText.trim();
1372
+ if (trimmed === "") {
1373
+ return null;
1374
+ }
1375
+ switch (valueKind) {
1376
+ case "number":
1377
+ case "integer":
1378
+ case "signedInteger":
1379
+ return Number.isFinite(Number(trimmed)) ? trimmed : JSON.stringify(trimmed);
1380
+ case "string":
1381
+ return JSON.stringify(argumentText);
1382
+ case "json":
1383
+ try {
1384
+ JSON.parse(trimmed);
1385
+ return `(${trimmed})`;
1386
+ } catch {
1387
+ return JSON.stringify(trimmed);
1388
+ }
1389
+ case "boolean":
1390
+ return trimmed === "true" || trimmed === "false" ? trimmed : JSON.stringify(trimmed);
1391
+ case "condition":
1392
+ return "undefined as unknown as FormSpecCondition";
1393
+ case null:
1394
+ return null;
1395
+ default: {
1396
+ return String(valueKind);
1397
+ }
1398
+ }
1399
+ }
1400
+ function getArrayElementType(type, checker) {
1401
+ if (!checker.isArrayType(type)) {
1402
+ return null;
1403
+ }
1404
+ return checker.getTypeArguments(type)[0] ?? null;
1405
+ }
1406
+ function supportsConstraintCapability(type, checker, capability) {
1407
+ if (capability === void 0) {
1408
+ return true;
1409
+ }
1410
+ if (hasTypeSemanticCapability(type, checker, capability)) {
1411
+ return true;
1412
+ }
1413
+ if (capability === "string-like") {
1414
+ const itemType = getArrayElementType(type, checker);
1415
+ return itemType !== null && hasTypeSemanticCapability(itemType, checker, capability);
1416
+ }
1417
+ return false;
1418
+ }
1419
+ function makeDiagnostic(code, message, provenance) {
1420
+ return {
1421
+ code,
1422
+ message,
1423
+ severity: "error",
1424
+ primaryLocation: provenance,
1425
+ relatedLocations: []
1426
+ };
1427
+ }
1428
+ function placementLabel(placement) {
1429
+ switch (placement) {
1430
+ case "class":
1431
+ return "class declarations";
1432
+ case "class-field":
1433
+ return "class fields";
1434
+ case "class-method":
1435
+ return "class methods";
1436
+ case "interface":
1437
+ return "interface declarations";
1438
+ case "interface-field":
1439
+ return "interface fields";
1440
+ case "type-alias":
1441
+ return "type aliases";
1442
+ case "type-alias-field":
1443
+ return "type-alias properties";
1444
+ case "variable":
1445
+ return "variables";
1446
+ case "function":
1447
+ return "functions";
1448
+ case "function-parameter":
1449
+ return "function parameters";
1450
+ case "method-parameter":
1451
+ return "method parameters";
1452
+ default: {
1453
+ const exhaustive = placement;
1454
+ return String(exhaustive);
1455
+ }
1456
+ }
1457
+ }
1458
+ function capabilityLabel(capability) {
1459
+ switch (capability) {
1460
+ case "numeric-comparable":
1461
+ return "number";
1462
+ case "string-like":
1463
+ return "string";
1464
+ case "array-like":
1465
+ return "array";
1466
+ case "enum-member-addressable":
1467
+ return "enum";
1468
+ case "json-like":
1469
+ return "JSON-compatible";
1470
+ case "object-like":
1471
+ return "object";
1472
+ case "condition-like":
1473
+ return "conditional";
1474
+ case void 0:
1475
+ return "compatible";
1476
+ default:
1477
+ return capability;
1478
+ }
1479
+ }
1480
+ function getBroadenedCustomTypeId(fieldType) {
1481
+ if (fieldType?.kind === "custom") {
1482
+ return fieldType.typeId;
1483
+ }
1484
+ if (fieldType?.kind !== "union") {
1485
+ return void 0;
1486
+ }
1487
+ const customMembers = fieldType.members.filter(
1488
+ (member) => member.kind === "custom"
1489
+ );
1490
+ if (customMembers.length !== 1) {
1491
+ return void 0;
1492
+ }
1493
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
1494
+ const allOtherMembersAreNull = nonCustomMembers.every(
1495
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
1496
+ );
1497
+ const customMember = customMembers[0];
1498
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
1499
+ }
1500
+ function hasBuiltinConstraintBroadening(tagName, options) {
1501
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
1502
+ return broadenedTypeId !== void 0 && options?.extensionRegistry?.findBuiltinConstraintBroadening(broadenedTypeId, tagName) !== void 0;
1503
+ }
1504
+ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, parsedTag, provenance, supportingDeclarations, options) {
1505
+ if (!isBuiltinConstraintName(tagName)) {
1506
+ return [];
1507
+ }
1508
+ const checker = options?.checker;
1509
+ const subjectType = options?.subjectType;
1510
+ if (checker === void 0 || subjectType === void 0) {
1511
+ return [];
1512
+ }
1513
+ const placement = resolveDeclarationPlacement(node);
1514
+ if (placement === null) {
1515
+ return [];
1516
+ }
1517
+ const definition = getTagDefinition(tagName, options?.extensionRegistry?.extensions);
1518
+ if (definition === null) {
1519
+ return [];
1520
+ }
1521
+ if (!definition.placements.includes(placement)) {
1522
+ return [
1523
+ makeDiagnostic(
1524
+ "INVALID_TAG_PLACEMENT",
1525
+ `Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
1526
+ provenance
1527
+ )
1528
+ ];
1529
+ }
1530
+ const target = parsedTag?.target ?? null;
1531
+ const hasBroadening = target === null && hasBuiltinConstraintBroadening(tagName, options);
1532
+ if (target !== null) {
1533
+ if (target.kind !== "path") {
1534
+ return [
1535
+ makeDiagnostic(
1536
+ "UNSUPPORTED_TARGETING_SYNTAX",
1537
+ `Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
1538
+ provenance
1539
+ )
1540
+ ];
1541
+ }
1542
+ if (!target.valid || target.path === null) {
1543
+ return [
1544
+ makeDiagnostic(
1545
+ "UNSUPPORTED_TARGETING_SYNTAX",
1546
+ `Tag "@${tagName}" has invalid path targeting syntax.`,
1547
+ provenance
1548
+ )
1549
+ ];
1550
+ }
1551
+ const resolution = resolvePathTargetType(subjectType, checker, target.path.segments);
1552
+ if (resolution.kind === "missing-property") {
1553
+ return [
1554
+ makeDiagnostic(
1555
+ "UNKNOWN_PATH_TARGET",
1556
+ `Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
1557
+ provenance
1558
+ )
1559
+ ];
1560
+ }
1561
+ if (resolution.kind === "unresolvable") {
1562
+ const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
1563
+ return [
1564
+ makeDiagnostic(
1565
+ "TYPE_MISMATCH",
1566
+ `Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
1567
+ provenance
1568
+ )
1569
+ ];
1570
+ }
1571
+ const requiredCapability = definition.capabilities[0];
1572
+ if (requiredCapability !== void 0 && !supportsConstraintCapability(resolution.type, checker, requiredCapability)) {
1573
+ const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
1574
+ return [
1575
+ makeDiagnostic(
1576
+ "TYPE_MISMATCH",
1577
+ `Target "${target.rawText}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
1578
+ provenance
1579
+ )
1580
+ ];
1581
+ }
1582
+ } else if (!hasBroadening) {
1583
+ const requiredCapability = definition.capabilities[0];
1584
+ if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
1585
+ const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
1586
+ return [
1587
+ makeDiagnostic(
1588
+ "TYPE_MISMATCH",
1589
+ `Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
1590
+ provenance
1591
+ )
1592
+ ];
1593
+ }
1594
+ }
1595
+ const argumentExpression = renderSyntheticArgumentExpression(
1596
+ definition.valueKind,
1597
+ parsedTag?.argumentText ?? ""
1598
+ );
1599
+ if (definition.requiresArgument && argumentExpression === null) {
1600
+ return [];
1601
+ }
1602
+ if (hasBroadening) {
1603
+ return [];
1604
+ }
1605
+ const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
1606
+ const hostType = options?.hostType ?? subjectType;
1607
+ const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
1608
+ const result = checkSyntheticTagApplication({
1609
+ tagName,
1610
+ placement,
1611
+ hostType: hostTypeText,
1612
+ subjectType: subjectTypeText,
1613
+ ...target?.kind === "path" ? { target: { kind: "path", text: target.rawText } } : {},
1614
+ ...argumentExpression !== null ? { argumentExpression } : {},
1615
+ supportingDeclarations,
1616
+ ...options?.extensionRegistry !== void 0 ? {
1617
+ extensions: options.extensionRegistry.extensions.map((extension) => ({
1618
+ extensionId: extension.extensionId,
1619
+ ...extension.constraintTags !== void 0 ? {
1620
+ constraintTags: extension.constraintTags.map((tag) => ({ tagName: tag.tagName }))
1621
+ } : {}
1622
+ }))
1623
+ } : {}
1624
+ });
1625
+ if (result.diagnostics.length === 0) {
1626
+ return [];
1627
+ }
1628
+ const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
1629
+ return [
1630
+ makeDiagnostic(
1631
+ "TYPE_MISMATCH",
1632
+ `Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
1633
+ provenance
1634
+ )
1635
+ ];
1636
+ }
1353
1637
  function getParser(options) {
1354
1638
  const extensionTagNames = [
1355
1639
  ...options?.extensionRegistry?.extensions.flatMap(
@@ -1365,18 +1649,54 @@ function getParser(options) {
1365
1649
  parserCache.set(cacheKey, parser);
1366
1650
  return parser;
1367
1651
  }
1652
+ function getExtensionRegistryCacheKey(registry) {
1653
+ if (registry === void 0) {
1654
+ return "";
1655
+ }
1656
+ return registry.extensions.map(
1657
+ (extension) => JSON.stringify({
1658
+ extensionId: extension.extensionId,
1659
+ typeNames: extension.types?.map((type) => type.typeName) ?? [],
1660
+ constraintTags: extension.constraintTags?.map((tag) => tag.tagName) ?? []
1661
+ })
1662
+ ).join("|");
1663
+ }
1664
+ function getParseCacheKey(node, file, options) {
1665
+ const sourceFile = node.getSourceFile();
1666
+ const checker = options?.checker;
1667
+ return JSON.stringify({
1668
+ file,
1669
+ sourceFile: sourceFile.fileName,
1670
+ sourceText: sourceFile.text,
1671
+ start: node.getFullStart(),
1672
+ end: node.getEnd(),
1673
+ fieldType: options?.fieldType ?? null,
1674
+ subjectType: checker !== void 0 && options?.subjectType !== void 0 ? checker.typeToString(options.subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
1675
+ hostType: checker !== void 0 && options?.hostType !== void 0 ? checker.typeToString(options.hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
1676
+ extensions: getExtensionRegistryCacheKey(options?.extensionRegistry)
1677
+ });
1678
+ }
1368
1679
  function parseTSDocTags(node, file = "", options) {
1680
+ const cacheKey = getParseCacheKey(node, file, options);
1681
+ const cached = parseResultCache.get(cacheKey);
1682
+ if (cached !== void 0) {
1683
+ return cached;
1684
+ }
1369
1685
  const constraints = [];
1370
1686
  const annotations = [];
1687
+ const diagnostics = [];
1371
1688
  let displayName;
1372
1689
  let description;
1373
1690
  let placeholder;
1374
1691
  let displayNameProvenance;
1375
1692
  let descriptionProvenance;
1376
1693
  let placeholderProvenance;
1694
+ const rawTextTags = [];
1377
1695
  const sourceFile = node.getSourceFile();
1378
1696
  const sourceText = sourceFile.getFullText();
1697
+ const supportingDeclarations = buildSupportingDeclarations(sourceFile);
1379
1698
  const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
1699
+ const rawTextFallbacks = collectRawTextFallbacks(node, file);
1380
1700
  if (commentRanges) {
1381
1701
  for (const range of commentRanges) {
1382
1702
  if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
@@ -1391,12 +1711,33 @@ function parseTSDocTags(node, file = "", options) {
1391
1711
  TextRange.fromStringRange(sourceText, range.pos, range.end)
1392
1712
  );
1393
1713
  const docComment = parserContext.docComment;
1714
+ const parsedComment = parseCommentBlock(
1715
+ commentText,
1716
+ sharedCommentSyntaxOptions(options, range.pos)
1717
+ );
1718
+ let parsedTagCursor = 0;
1719
+ const nextParsedTag = (normalizedTagName) => {
1720
+ while (parsedTagCursor < parsedComment.tags.length) {
1721
+ const candidate = parsedComment.tags[parsedTagCursor];
1722
+ parsedTagCursor += 1;
1723
+ if (candidate?.normalizedTagName === normalizedTagName) {
1724
+ return candidate;
1725
+ }
1726
+ }
1727
+ return null;
1728
+ };
1729
+ for (const parsedTag of parsedComment.tags) {
1730
+ if (TAGS_REQUIRING_RAW_TEXT.has(parsedTag.normalizedTagName)) {
1731
+ rawTextTags.push({ tag: parsedTag, commentText, commentOffset: range.pos });
1732
+ }
1733
+ }
1394
1734
  for (const block of docComment.customBlocks) {
1395
1735
  const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
1736
+ const parsedTag = nextParsedTag(tagName);
1396
1737
  if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1397
- const text2 = extractBlockText(block).trim();
1738
+ const text2 = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
1398
1739
  if (text2 === "") continue;
1399
- const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1740
+ const provenance2 = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
1400
1741
  switch (tagName) {
1401
1742
  case "displayName":
1402
1743
  if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
@@ -1426,11 +1767,29 @@ function parseTSDocTags(node, file = "", options) {
1426
1767
  continue;
1427
1768
  }
1428
1769
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1429
- const text = extractBlockText(block).trim();
1770
+ const text = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
1430
1771
  const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1431
1772
  if (text === "" && expectedType !== "boolean") continue;
1432
- const provenance = provenanceForComment(range, sourceFile, file, tagName);
1433
- const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1773
+ const provenance = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
1774
+ const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
1775
+ node,
1776
+ sourceFile,
1777
+ tagName,
1778
+ parsedTag,
1779
+ provenance,
1780
+ supportingDeclarations,
1781
+ options
1782
+ );
1783
+ if (compilerDiagnostics.length > 0) {
1784
+ diagnostics.push(...compilerDiagnostics);
1785
+ continue;
1786
+ }
1787
+ const constraintNode = parseConstraintTagValue(
1788
+ tagName,
1789
+ text,
1790
+ provenance,
1791
+ sharedTagValueOptions(options)
1792
+ );
1434
1793
  if (constraintNode) {
1435
1794
  constraints.push(constraintNode);
1436
1795
  }
@@ -1484,57 +1843,114 @@ function parseTSDocTags(node, file = "", options) {
1484
1843
  provenance: placeholderProvenance
1485
1844
  });
1486
1845
  }
1487
- const jsDocTagsAll = ts.getJSDocTags(node);
1488
- for (const tag of jsDocTagsAll) {
1489
- const tagName = normalizeConstraintTagName(tag.tagName.text);
1490
- if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1491
- const commentText = getTagCommentText(tag);
1492
- if (commentText === void 0 || commentText.trim() === "") continue;
1493
- const text = commentText.trim();
1494
- const provenance = provenanceForJSDocTag(tag, file);
1495
- if (tagName === "defaultValue") {
1496
- const defaultValueNode = parseDefaultValueValue(text, provenance);
1497
- annotations.push(defaultValueNode);
1498
- continue;
1846
+ if (rawTextTags.length > 0) {
1847
+ for (const rawTextTag of rawTextTags) {
1848
+ const fallbackQueue = rawTextFallbacks.get(rawTextTag.tag.normalizedTagName);
1849
+ const fallback = fallbackQueue?.shift();
1850
+ const text = choosePreferredPayloadText(
1851
+ getSharedPayloadText(rawTextTag.tag, rawTextTag.commentText, rawTextTag.commentOffset),
1852
+ fallback?.text ?? ""
1853
+ );
1854
+ if (text === "") continue;
1855
+ const provenance = provenanceForParsedTag(rawTextTag.tag, sourceFile, file);
1856
+ if (rawTextTag.tag.normalizedTagName === "defaultValue") {
1857
+ const defaultValueNode = parseDefaultValueTagValue(text, provenance);
1858
+ annotations.push(defaultValueNode);
1859
+ continue;
1860
+ }
1861
+ const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
1862
+ node,
1863
+ sourceFile,
1864
+ rawTextTag.tag.normalizedTagName,
1865
+ rawTextTag.tag,
1866
+ provenance,
1867
+ supportingDeclarations,
1868
+ options
1869
+ );
1870
+ if (compilerDiagnostics.length > 0) {
1871
+ diagnostics.push(...compilerDiagnostics);
1872
+ continue;
1873
+ }
1874
+ const constraintNode = parseConstraintTagValue(
1875
+ rawTextTag.tag.normalizedTagName,
1876
+ text,
1877
+ provenance,
1878
+ sharedTagValueOptions(options)
1879
+ );
1880
+ if (constraintNode) {
1881
+ constraints.push(constraintNode);
1882
+ }
1499
1883
  }
1500
- const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1501
- if (constraintNode) {
1502
- constraints.push(constraintNode);
1884
+ }
1885
+ for (const [tagName, fallbacks] of rawTextFallbacks) {
1886
+ for (const fallback of fallbacks) {
1887
+ const text = fallback.text.trim();
1888
+ if (text === "") continue;
1889
+ const provenance = fallback.provenance;
1890
+ if (tagName === "defaultValue") {
1891
+ const defaultValueNode = parseDefaultValueTagValue(text, provenance);
1892
+ annotations.push(defaultValueNode);
1893
+ continue;
1894
+ }
1895
+ const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
1896
+ node,
1897
+ sourceFile,
1898
+ tagName,
1899
+ null,
1900
+ provenance,
1901
+ supportingDeclarations,
1902
+ options
1903
+ );
1904
+ if (compilerDiagnostics.length > 0) {
1905
+ diagnostics.push(...compilerDiagnostics);
1906
+ continue;
1907
+ }
1908
+ const constraintNode = parseConstraintTagValue(
1909
+ tagName,
1910
+ text,
1911
+ provenance,
1912
+ sharedTagValueOptions(options)
1913
+ );
1914
+ if (constraintNode) {
1915
+ constraints.push(constraintNode);
1916
+ }
1503
1917
  }
1504
1918
  }
1505
- return { constraints, annotations };
1919
+ const result = { constraints, annotations, diagnostics };
1920
+ parseResultCache.set(cacheKey, result);
1921
+ return result;
1506
1922
  }
1507
1923
  function extractDisplayNameMetadata(node) {
1508
1924
  let displayName;
1509
1925
  const memberDisplayNames = /* @__PURE__ */ new Map();
1510
- for (const tag of ts.getJSDocTags(node)) {
1511
- const tagName = normalizeConstraintTagName(tag.tagName.text);
1512
- if (tagName !== "displayName") continue;
1513
- const commentText = getTagCommentText(tag);
1514
- if (commentText === void 0) continue;
1515
- const text = commentText.trim();
1516
- if (text === "") continue;
1517
- const memberTarget = parseMemberTargetDisplayName(text);
1518
- if (memberTarget) {
1519
- memberDisplayNames.set(memberTarget.target, memberTarget.label);
1520
- continue;
1926
+ const sourceFile = node.getSourceFile();
1927
+ const sourceText = sourceFile.getFullText();
1928
+ const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
1929
+ if (commentRanges) {
1930
+ for (const range of commentRanges) {
1931
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) continue;
1932
+ const commentText = sourceText.substring(range.pos, range.end);
1933
+ if (!commentText.startsWith("/**")) continue;
1934
+ const parsed = parseCommentBlock(commentText);
1935
+ for (const tag of parsed.tags) {
1936
+ if (tag.normalizedTagName !== "displayName") {
1937
+ continue;
1938
+ }
1939
+ if (tag.target !== null && tag.argumentText !== "") {
1940
+ memberDisplayNames.set(tag.target.rawText, tag.argumentText);
1941
+ continue;
1942
+ }
1943
+ if (tag.argumentText !== "") {
1944
+ displayName ??= tag.argumentText;
1945
+ }
1946
+ }
1521
1947
  }
1522
- displayName ??= text;
1523
1948
  }
1524
1949
  return {
1525
1950
  ...displayName !== void 0 && { displayName },
1526
1951
  memberDisplayNames
1527
1952
  };
1528
1953
  }
1529
- function extractPathTarget(text) {
1530
- const trimmed = text.trimStart();
1531
- const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
1532
- if (!match?.[1]) return null;
1533
- return {
1534
- path: { segments: [match[1]] },
1535
- remainingText: match[2] ?? ""
1536
- };
1537
- }
1538
1954
  function extractBlockText(block) {
1539
1955
  return extractPlainText(block.content);
1540
1956
  }
@@ -1553,281 +1969,111 @@ function extractPlainText(node) {
1553
1969
  }
1554
1970
  return result;
1555
1971
  }
1556
- function parseConstraintValue(tagName, text, provenance, options) {
1557
- const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
1558
- if (customConstraint) {
1559
- return customConstraint;
1560
- }
1561
- if (!isBuiltinConstraintName(tagName)) {
1562
- return null;
1972
+ function choosePreferredPayloadText(primary, fallback) {
1973
+ const preferred = primary.trim();
1974
+ const alternate = fallback.trim();
1975
+ if (preferred === "") return alternate;
1976
+ if (alternate === "") return preferred;
1977
+ if (alternate.includes("\n")) return alternate;
1978
+ if (alternate.length > preferred.length && alternate.startsWith(preferred)) {
1979
+ return alternate;
1563
1980
  }
1564
- const pathResult = extractPathTarget(text);
1565
- const effectiveText = pathResult ? pathResult.remainingText : text;
1566
- const path4 = pathResult?.path;
1567
- const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
1568
- if (expectedType === "number") {
1569
- const value = Number(effectiveText);
1570
- if (Number.isNaN(value)) {
1571
- return null;
1572
- }
1573
- const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
1574
- if (numericKind) {
1575
- return {
1576
- kind: "constraint",
1577
- constraintKind: numericKind,
1578
- value,
1579
- ...path4 && { path: path4 },
1580
- provenance
1581
- };
1582
- }
1583
- const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
1584
- if (lengthKind) {
1585
- return {
1586
- kind: "constraint",
1587
- constraintKind: lengthKind,
1588
- value,
1589
- ...path4 && { path: path4 },
1590
- provenance
1591
- };
1592
- }
1593
- return null;
1594
- }
1595
- if (expectedType === "boolean") {
1596
- const trimmed = effectiveText.trim();
1597
- if (trimmed !== "" && trimmed !== "true") {
1598
- return null;
1599
- }
1600
- if (tagName === "uniqueItems") {
1601
- return {
1602
- kind: "constraint",
1603
- constraintKind: "uniqueItems",
1604
- value: true,
1605
- ...path4 && { path: path4 },
1606
- provenance
1607
- };
1608
- }
1609
- return null;
1981
+ return preferred;
1982
+ }
1983
+ function getSharedPayloadText(tag, commentText, commentOffset) {
1984
+ if (tag.payloadSpan === null) {
1985
+ return "";
1610
1986
  }
1611
- if (expectedType === "json") {
1612
- if (tagName === "const") {
1613
- const trimmedText = effectiveText.trim();
1614
- if (trimmedText === "") return null;
1615
- try {
1616
- const parsed2 = JSON.parse(trimmedText);
1617
- return {
1618
- kind: "constraint",
1619
- constraintKind: "const",
1620
- value: parsed2,
1621
- ...path4 && { path: path4 },
1622
- provenance
1623
- };
1624
- } catch {
1625
- return {
1626
- kind: "constraint",
1627
- constraintKind: "const",
1628
- value: trimmedText,
1629
- ...path4 && { path: path4 },
1630
- provenance
1631
- };
1632
- }
1633
- }
1634
- const parsed = tryParseJson(effectiveText);
1635
- if (!Array.isArray(parsed)) {
1636
- return null;
1637
- }
1638
- const members = [];
1639
- for (const item of parsed) {
1640
- if (typeof item === "string" || typeof item === "number") {
1641
- members.push(item);
1642
- } else if (typeof item === "object" && item !== null && "id" in item) {
1643
- const id = item["id"];
1644
- if (typeof id === "string" || typeof id === "number") {
1645
- members.push(id);
1646
- }
1647
- }
1648
- }
1649
- return {
1650
- kind: "constraint",
1651
- constraintKind: "allowedMembers",
1652
- members,
1653
- ...path4 && { path: path4 },
1654
- provenance
1655
- };
1987
+ return sliceCommentSpan(commentText, tag.payloadSpan, {
1988
+ offset: commentOffset
1989
+ }).trim();
1990
+ }
1991
+ function getBestBlockPayloadText(tag, commentText, commentOffset, block) {
1992
+ const sharedText = tag === null ? "" : getSharedPayloadText(tag, commentText, commentOffset);
1993
+ const blockText = extractBlockText(block).replace(/\s+/g, " ").trim();
1994
+ return choosePreferredPayloadText(sharedText, blockText);
1995
+ }
1996
+ function collectRawTextFallbacks(node, file) {
1997
+ const fallbacks = /* @__PURE__ */ new Map();
1998
+ for (const tag of ts.getJSDocTags(node)) {
1999
+ const tagName = normalizeConstraintTagName(tag.tagName.text);
2000
+ if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
2001
+ const commentText = getTagCommentText(tag)?.trim() ?? "";
2002
+ if (commentText === "") continue;
2003
+ const entries = fallbacks.get(tagName) ?? [];
2004
+ entries.push({
2005
+ text: commentText,
2006
+ provenance: provenanceForJSDocTag(tag, file)
2007
+ });
2008
+ fallbacks.set(tagName, entries);
1656
2009
  }
2010
+ return fallbacks;
2011
+ }
2012
+ function isMemberTargetDisplayName(text) {
2013
+ return parseTagSyntax("displayName", text).target !== null;
2014
+ }
2015
+ function provenanceForComment(range, sourceFile, file, tagName) {
2016
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1657
2017
  return {
1658
- kind: "constraint",
1659
- constraintKind: "pattern",
1660
- pattern: effectiveText,
1661
- ...path4 && { path: path4 },
1662
- provenance
2018
+ surface: "tsdoc",
2019
+ file,
2020
+ line: line + 1,
2021
+ column: character,
2022
+ tagName: "@" + tagName
1663
2023
  };
1664
2024
  }
1665
- function parseExtensionConstraintValue(tagName, text, provenance, options) {
1666
- const pathResult = extractPathTarget(text);
1667
- const effectiveText = pathResult ? pathResult.remainingText : text;
1668
- const path4 = pathResult?.path;
1669
- const registry = options?.extensionRegistry;
1670
- if (registry === void 0) {
1671
- return null;
2025
+ function provenanceForParsedTag(tag, sourceFile, file) {
2026
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.tagNameSpan.start);
2027
+ return {
2028
+ surface: "tsdoc",
2029
+ file,
2030
+ line: line + 1,
2031
+ column: character,
2032
+ tagName: "@" + tag.normalizedTagName
2033
+ };
2034
+ }
2035
+ function provenanceForJSDocTag(tag, file) {
2036
+ const sourceFile = tag.getSourceFile();
2037
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
2038
+ return {
2039
+ surface: "tsdoc",
2040
+ file,
2041
+ line: line + 1,
2042
+ column: character,
2043
+ tagName: "@" + tag.tagName.text
2044
+ };
2045
+ }
2046
+ function getTagCommentText(tag) {
2047
+ if (tag.comment === void 0) {
2048
+ return void 0;
1672
2049
  }
1673
- const directTag = registry.findConstraintTag(tagName);
1674
- if (directTag !== void 0) {
1675
- return makeCustomConstraintNode(
1676
- directTag.extensionId,
1677
- directTag.registration.constraintName,
1678
- directTag.registration.parseValue(effectiveText),
1679
- provenance,
1680
- path4,
1681
- registry
1682
- );
1683
- }
1684
- if (!isBuiltinConstraintName(tagName)) {
1685
- return null;
1686
- }
1687
- const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
1688
- if (broadenedTypeId === void 0) {
1689
- return null;
1690
- }
1691
- const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
1692
- if (broadened === void 0) {
1693
- return null;
1694
- }
1695
- return makeCustomConstraintNode(
1696
- broadened.extensionId,
1697
- broadened.registration.constraintName,
1698
- broadened.registration.parseValue(effectiveText),
1699
- provenance,
1700
- path4,
1701
- registry
1702
- );
1703
- }
1704
- function getBroadenedCustomTypeId(fieldType) {
1705
- if (fieldType?.kind === "custom") {
1706
- return fieldType.typeId;
1707
- }
1708
- if (fieldType?.kind !== "union") {
1709
- return void 0;
1710
- }
1711
- const customMembers = fieldType.members.filter(
1712
- (member) => member.kind === "custom"
1713
- );
1714
- if (customMembers.length !== 1) {
1715
- return void 0;
1716
- }
1717
- const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
1718
- const allOtherMembersAreNull = nonCustomMembers.every(
1719
- (member) => member.kind === "primitive" && member.primitiveKind === "null"
1720
- );
1721
- const customMember = customMembers[0];
1722
- return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
1723
- }
1724
- function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path4, registry) {
1725
- const constraintId = `${extensionId}/${constraintName}`;
1726
- const registration = registry.findConstraint(constraintId);
1727
- if (registration === void 0) {
1728
- throw new Error(
1729
- `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
1730
- );
1731
- }
1732
- return {
1733
- kind: "constraint",
1734
- constraintKind: "custom",
1735
- constraintId,
1736
- payload,
1737
- compositionRule: registration.compositionRule,
1738
- ...path4 && { path: path4 },
1739
- provenance
1740
- };
1741
- }
1742
- function parseDefaultValueValue(text, provenance) {
1743
- const trimmed = text.trim();
1744
- let value;
1745
- if (trimmed === "null") {
1746
- value = null;
1747
- } else if (trimmed === "true") {
1748
- value = true;
1749
- } else if (trimmed === "false") {
1750
- value = false;
1751
- } else {
1752
- const parsed = tryParseJson(trimmed);
1753
- value = parsed !== null ? parsed : trimmed;
1754
- }
1755
- return {
1756
- kind: "annotation",
1757
- annotationKind: "defaultValue",
1758
- value,
1759
- provenance
1760
- };
1761
- }
1762
- function isMemberTargetDisplayName(text) {
1763
- return parseMemberTargetDisplayName(text) !== null;
1764
- }
1765
- function parseMemberTargetDisplayName(text) {
1766
- const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1767
- if (!match?.[1] || !match[2]) return null;
1768
- return { target: match[1], label: match[2].trim() };
1769
- }
1770
- function provenanceForComment(range, sourceFile, file, tagName) {
1771
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1772
- return {
1773
- surface: "tsdoc",
1774
- file,
1775
- line: line + 1,
1776
- column: character,
1777
- tagName: "@" + tagName
1778
- };
1779
- }
1780
- function provenanceForJSDocTag(tag, file) {
1781
- const sourceFile = tag.getSourceFile();
1782
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
1783
- return {
1784
- surface: "tsdoc",
1785
- file,
1786
- line: line + 1,
1787
- column: character,
1788
- tagName: "@" + tag.tagName.text
1789
- };
1790
- }
1791
- function getTagCommentText(tag) {
1792
- if (tag.comment === void 0) {
1793
- return void 0;
1794
- }
1795
- if (typeof tag.comment === "string") {
1796
- return tag.comment;
2050
+ if (typeof tag.comment === "string") {
2051
+ return tag.comment;
1797
2052
  }
1798
2053
  return ts.getTextOfJSDocComment(tag.comment);
1799
2054
  }
1800
- var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, parserCache;
2055
+ var TAGS_REQUIRING_RAW_TEXT, SYNTHETIC_TYPE_FORMAT_FLAGS, parserCache, parseResultCache;
1801
2056
  var init_tsdoc_parser = __esm({
1802
2057
  "src/analyzer/tsdoc-parser.ts"() {
1803
2058
  "use strict";
1804
- init_json_utils();
1805
- NUMERIC_CONSTRAINT_MAP = {
1806
- minimum: "minimum",
1807
- maximum: "maximum",
1808
- exclusiveMinimum: "exclusiveMinimum",
1809
- exclusiveMaximum: "exclusiveMaximum",
1810
- multipleOf: "multipleOf"
1811
- };
1812
- LENGTH_CONSTRAINT_MAP = {
1813
- minLength: "minLength",
1814
- maxLength: "maxLength",
1815
- minItems: "minItems",
1816
- maxItems: "maxItems"
1817
- };
1818
2059
  TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
2060
+ SYNTHETIC_TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
1819
2061
  parserCache = /* @__PURE__ */ new Map();
2062
+ parseResultCache = /* @__PURE__ */ new Map();
1820
2063
  }
1821
2064
  });
1822
2065
 
1823
2066
  // src/analyzer/jsdoc-constraints.ts
1824
2067
  import * as ts2 from "typescript";
2068
+ function extractJSDocParseResult(node, file = "", options) {
2069
+ return parseTSDocTags(node, file, options);
2070
+ }
1825
2071
  function extractJSDocConstraintNodes(node, file = "", options) {
1826
- const result = parseTSDocTags(node, file, options);
2072
+ const result = extractJSDocParseResult(node, file, options);
1827
2073
  return [...result.constraints];
1828
2074
  }
1829
2075
  function extractJSDocAnnotationNodes(node, file = "", options) {
1830
- const result = parseTSDocTags(node, file, options);
2076
+ const result = extractJSDocParseResult(node, file, options);
1831
2077
  return [...result.annotations];
1832
2078
  }
1833
2079
  function extractDefaultValueAnnotation(initializer, file = "") {
@@ -1878,13 +2124,16 @@ function isObjectType(type) {
1878
2124
  function isTypeReference(type) {
1879
2125
  return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
1880
2126
  }
1881
- function makeParseOptions(extensionRegistry, fieldType) {
1882
- if (extensionRegistry === void 0 && fieldType === void 0) {
2127
+ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, hostType) {
2128
+ if (extensionRegistry === void 0 && fieldType === void 0 && checker === void 0 && subjectType === void 0 && hostType === void 0) {
1883
2129
  return void 0;
1884
2130
  }
1885
2131
  return {
1886
2132
  ...extensionRegistry !== void 0 && { extensionRegistry },
1887
- ...fieldType !== void 0 && { fieldType }
2133
+ ...fieldType !== void 0 && { fieldType },
2134
+ ...checker !== void 0 && { checker },
2135
+ ...subjectType !== void 0 && { subjectType },
2136
+ ...hostType !== void 0 && { hostType }
1888
2137
  };
1889
2138
  }
1890
2139
  function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
@@ -1892,11 +2141,15 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1892
2141
  const fields = [];
1893
2142
  const fieldLayouts = [];
1894
2143
  const typeRegistry = {};
1895
- const annotations = extractJSDocAnnotationNodes(
2144
+ const diagnostics = [];
2145
+ const classType = checker.getTypeAtLocation(classDecl);
2146
+ const classDoc = extractJSDocParseResult(
1896
2147
  classDecl,
1897
2148
  file,
1898
- makeParseOptions(extensionRegistry)
2149
+ makeParseOptions(extensionRegistry, void 0, checker, classType, classType)
1899
2150
  );
2151
+ const annotations = [...classDoc.annotations];
2152
+ diagnostics.push(...classDoc.diagnostics);
1900
2153
  const visiting = /* @__PURE__ */ new Set();
1901
2154
  const instanceMethods = [];
1902
2155
  const staticMethods = [];
@@ -1908,6 +2161,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1908
2161
  file,
1909
2162
  typeRegistry,
1910
2163
  visiting,
2164
+ diagnostics,
2165
+ classType,
1911
2166
  extensionRegistry
1912
2167
  );
1913
2168
  if (fieldNode) {
@@ -1932,6 +2187,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1932
2187
  fieldLayouts,
1933
2188
  typeRegistry,
1934
2189
  ...annotations.length > 0 && { annotations },
2190
+ ...diagnostics.length > 0 && { diagnostics },
1935
2191
  instanceMethods,
1936
2192
  staticMethods
1937
2193
  };
@@ -1940,11 +2196,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1940
2196
  const name = interfaceDecl.name.text;
1941
2197
  const fields = [];
1942
2198
  const typeRegistry = {};
1943
- const annotations = extractJSDocAnnotationNodes(
2199
+ const diagnostics = [];
2200
+ const interfaceType = checker.getTypeAtLocation(interfaceDecl);
2201
+ const interfaceDoc = extractJSDocParseResult(
1944
2202
  interfaceDecl,
1945
2203
  file,
1946
- makeParseOptions(extensionRegistry)
2204
+ makeParseOptions(extensionRegistry, void 0, checker, interfaceType, interfaceType)
1947
2205
  );
2206
+ const annotations = [...interfaceDoc.annotations];
2207
+ diagnostics.push(...interfaceDoc.diagnostics);
1948
2208
  const visiting = /* @__PURE__ */ new Set();
1949
2209
  for (const member of interfaceDecl.members) {
1950
2210
  if (ts3.isPropertySignature(member)) {
@@ -1954,6 +2214,8 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1954
2214
  file,
1955
2215
  typeRegistry,
1956
2216
  visiting,
2217
+ diagnostics,
2218
+ interfaceType,
1957
2219
  extensionRegistry
1958
2220
  );
1959
2221
  if (fieldNode) {
@@ -1968,6 +2230,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1968
2230
  fieldLayouts,
1969
2231
  typeRegistry,
1970
2232
  ...annotations.length > 0 && { annotations },
2233
+ ...diagnostics.length > 0 && { diagnostics },
1971
2234
  instanceMethods: [],
1972
2235
  staticMethods: []
1973
2236
  };
@@ -1985,11 +2248,15 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1985
2248
  const name = typeAlias.name.text;
1986
2249
  const fields = [];
1987
2250
  const typeRegistry = {};
1988
- const annotations = extractJSDocAnnotationNodes(
2251
+ const diagnostics = [];
2252
+ const aliasType = checker.getTypeAtLocation(typeAlias);
2253
+ const typeAliasDoc = extractJSDocParseResult(
1989
2254
  typeAlias,
1990
2255
  file,
1991
- makeParseOptions(extensionRegistry)
2256
+ makeParseOptions(extensionRegistry, void 0, checker, aliasType, aliasType)
1992
2257
  );
2258
+ const annotations = [...typeAliasDoc.annotations];
2259
+ diagnostics.push(...typeAliasDoc.diagnostics);
1993
2260
  const visiting = /* @__PURE__ */ new Set();
1994
2261
  for (const member of typeAlias.type.members) {
1995
2262
  if (ts3.isPropertySignature(member)) {
@@ -1999,6 +2266,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1999
2266
  file,
2000
2267
  typeRegistry,
2001
2268
  visiting,
2269
+ diagnostics,
2270
+ aliasType,
2002
2271
  extensionRegistry
2003
2272
  );
2004
2273
  if (fieldNode) {
@@ -2014,12 +2283,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2014
2283
  fieldLayouts: fields.map(() => ({})),
2015
2284
  typeRegistry,
2016
2285
  ...annotations.length > 0 && { annotations },
2286
+ ...diagnostics.length > 0 && { diagnostics },
2017
2287
  instanceMethods: [],
2018
2288
  staticMethods: []
2019
2289
  }
2020
2290
  };
2021
2291
  }
2022
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
2292
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2023
2293
  if (!ts3.isIdentifier(prop.name)) {
2024
2294
  return null;
2025
2295
  }
@@ -2034,7 +2304,8 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
2034
2304
  typeRegistry,
2035
2305
  visiting,
2036
2306
  prop,
2037
- extensionRegistry
2307
+ extensionRegistry,
2308
+ diagnostics
2038
2309
  );
2039
2310
  const constraints = [];
2040
2311
  if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
@@ -2042,13 +2313,15 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
2042
2313
  ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2043
2314
  );
2044
2315
  }
2045
- constraints.push(
2046
- ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
2316
+ const docResult = extractJSDocParseResult(
2317
+ prop,
2318
+ file,
2319
+ makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
2047
2320
  );
2321
+ constraints.push(...docResult.constraints);
2322
+ diagnostics.push(...docResult.diagnostics);
2048
2323
  let annotations = [];
2049
- annotations.push(
2050
- ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2051
- );
2324
+ annotations.push(...docResult.annotations);
2052
2325
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
2053
2326
  if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
2054
2327
  annotations.push(defaultAnnotation);
@@ -2064,7 +2337,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
2064
2337
  provenance
2065
2338
  };
2066
2339
  }
2067
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
2340
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2068
2341
  if (!ts3.isIdentifier(prop.name)) {
2069
2342
  return null;
2070
2343
  }
@@ -2079,7 +2352,8 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2079
2352
  typeRegistry,
2080
2353
  visiting,
2081
2354
  prop,
2082
- extensionRegistry
2355
+ extensionRegistry,
2356
+ diagnostics
2083
2357
  );
2084
2358
  const constraints = [];
2085
2359
  if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
@@ -2087,13 +2361,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2087
2361
  ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2088
2362
  );
2089
2363
  }
2090
- constraints.push(
2091
- ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
2364
+ const docResult = extractJSDocParseResult(
2365
+ prop,
2366
+ file,
2367
+ makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
2092
2368
  );
2369
+ constraints.push(...docResult.constraints);
2370
+ diagnostics.push(...docResult.diagnostics);
2093
2371
  let annotations = [];
2094
- annotations.push(
2095
- ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2096
- );
2372
+ annotations.push(...docResult.annotations);
2097
2373
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
2098
2374
  return {
2099
2375
  kind: "field",
@@ -2222,7 +2498,7 @@ function getTypeNodeRegistrationName(typeNode) {
2222
2498
  }
2223
2499
  return null;
2224
2500
  }
2225
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2501
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2226
2502
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2227
2503
  if (customType) {
2228
2504
  return customType;
@@ -2234,7 +2510,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2234
2510
  typeRegistry,
2235
2511
  visiting,
2236
2512
  sourceNode,
2237
- extensionRegistry
2513
+ extensionRegistry,
2514
+ diagnostics
2238
2515
  );
2239
2516
  if (primitiveAlias) {
2240
2517
  return primitiveAlias;
@@ -2277,7 +2554,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2277
2554
  typeRegistry,
2278
2555
  visiting,
2279
2556
  sourceNode,
2280
- extensionRegistry
2557
+ extensionRegistry,
2558
+ diagnostics
2281
2559
  );
2282
2560
  }
2283
2561
  if (checker.isArrayType(type)) {
@@ -2288,15 +2566,24 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2288
2566
  typeRegistry,
2289
2567
  visiting,
2290
2568
  sourceNode,
2291
- extensionRegistry
2569
+ extensionRegistry,
2570
+ diagnostics
2292
2571
  );
2293
2572
  }
2294
2573
  if (isObjectType(type)) {
2295
- return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
2574
+ return resolveObjectType(
2575
+ type,
2576
+ checker,
2577
+ file,
2578
+ typeRegistry,
2579
+ visiting,
2580
+ extensionRegistry,
2581
+ diagnostics
2582
+ );
2296
2583
  }
2297
2584
  return { kind: "primitive", primitiveKind: "string" };
2298
2585
  }
2299
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2586
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2300
2587
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2301
2588
  return null;
2302
2589
  }
@@ -2324,7 +2611,8 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
2324
2611
  file,
2325
2612
  typeRegistry,
2326
2613
  visiting,
2327
- extensionRegistry
2614
+ extensionRegistry,
2615
+ diagnostics
2328
2616
  ),
2329
2617
  ...constraints.length > 0 && { constraints },
2330
2618
  ...annotations.length > 0 && { annotations },
@@ -2351,7 +2639,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2351
2639
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2352
2640
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2353
2641
  }
2354
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2642
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
2355
2643
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2356
2644
  if (nestedAliasDecl !== void 0) {
2357
2645
  return resolveAliasedPrimitiveTarget(
@@ -2360,12 +2648,22 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2360
2648
  file,
2361
2649
  typeRegistry,
2362
2650
  visiting,
2363
- extensionRegistry
2651
+ extensionRegistry,
2652
+ diagnostics
2364
2653
  );
2365
2654
  }
2366
- return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
2655
+ return resolveTypeNode(
2656
+ type,
2657
+ checker,
2658
+ file,
2659
+ typeRegistry,
2660
+ visiting,
2661
+ void 0,
2662
+ extensionRegistry,
2663
+ diagnostics
2664
+ );
2367
2665
  }
2368
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2666
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2369
2667
  const typeName = getNamedTypeName(type);
2370
2668
  const namedDecl = getNamedTypeDeclaration(type);
2371
2669
  if (typeName && typeName in typeRegistry) {
@@ -2455,7 +2753,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2455
2753
  typeRegistry,
2456
2754
  visiting,
2457
2755
  nonNullMembers[0].sourceNode ?? sourceNode,
2458
- extensionRegistry
2756
+ extensionRegistry,
2757
+ diagnostics
2459
2758
  );
2460
2759
  const result = hasNull ? {
2461
2760
  kind: "union",
@@ -2471,7 +2770,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2471
2770
  typeRegistry,
2472
2771
  visiting,
2473
2772
  memberSourceNode ?? sourceNode,
2474
- extensionRegistry
2773
+ extensionRegistry,
2774
+ diagnostics
2475
2775
  )
2476
2776
  );
2477
2777
  if (hasNull) {
@@ -2479,7 +2779,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2479
2779
  }
2480
2780
  return registerNamed({ kind: "union", members });
2481
2781
  }
2482
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2782
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2483
2783
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2484
2784
  const elementType = typeArgs?.[0];
2485
2785
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -2490,11 +2790,12 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
2490
2790
  typeRegistry,
2491
2791
  visiting,
2492
2792
  elementSourceNode,
2493
- extensionRegistry
2793
+ extensionRegistry,
2794
+ diagnostics
2494
2795
  ) : { kind: "primitive", primitiveKind: "string" };
2495
2796
  return { kind: "array", items };
2496
2797
  }
2497
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2798
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
2498
2799
  if (type.getProperties().length > 0) {
2499
2800
  return null;
2500
2801
  }
@@ -2509,7 +2810,8 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
2509
2810
  typeRegistry,
2510
2811
  visiting,
2511
2812
  void 0,
2512
- extensionRegistry
2813
+ extensionRegistry,
2814
+ diagnostics
2513
2815
  );
2514
2816
  return { kind: "record", valueType };
2515
2817
  }
@@ -2538,7 +2840,7 @@ function typeNodeContainsReference(type, targetName) {
2538
2840
  }
2539
2841
  }
2540
2842
  }
2541
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2843
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
2542
2844
  const typeName = getNamedTypeName(type);
2543
2845
  const namedTypeName = typeName ?? void 0;
2544
2846
  const namedDecl = getNamedTypeDeclaration(type);
@@ -2575,7 +2877,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2575
2877
  file,
2576
2878
  typeRegistry,
2577
2879
  visiting,
2578
- extensionRegistry
2880
+ extensionRegistry,
2881
+ diagnostics
2579
2882
  );
2580
2883
  if (recordNode) {
2581
2884
  visiting.delete(type);
@@ -2603,6 +2906,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2603
2906
  file,
2604
2907
  typeRegistry,
2605
2908
  visiting,
2909
+ diagnostics ?? [],
2606
2910
  extensionRegistry
2607
2911
  );
2608
2912
  for (const prop of type.getProperties()) {
@@ -2617,7 +2921,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2617
2921
  typeRegistry,
2618
2922
  visiting,
2619
2923
  declaration,
2620
- extensionRegistry
2924
+ extensionRegistry,
2925
+ diagnostics
2621
2926
  );
2622
2927
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2623
2928
  properties.push({
@@ -2647,7 +2952,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2647
2952
  }
2648
2953
  return objectNode;
2649
2954
  }
2650
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2955
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
2651
2956
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
2652
2957
  (s) => s?.declarations != null && s.declarations.length > 0
2653
2958
  );
@@ -2657,6 +2962,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2657
2962
  const classDecl = declarations.find(ts3.isClassDeclaration);
2658
2963
  if (classDecl) {
2659
2964
  const map = /* @__PURE__ */ new Map();
2965
+ const hostType = checker.getTypeAtLocation(classDecl);
2660
2966
  for (const member of classDecl.members) {
2661
2967
  if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
2662
2968
  const fieldNode = analyzeFieldToIR(
@@ -2665,6 +2971,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2665
2971
  file,
2666
2972
  typeRegistry,
2667
2973
  visiting,
2974
+ diagnostics,
2975
+ hostType,
2668
2976
  extensionRegistry
2669
2977
  );
2670
2978
  if (fieldNode) {
@@ -2686,6 +2994,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2686
2994
  file,
2687
2995
  typeRegistry,
2688
2996
  visiting,
2997
+ checker.getTypeAtLocation(interfaceDecl),
2998
+ diagnostics,
2689
2999
  extensionRegistry
2690
3000
  );
2691
3001
  }
@@ -2697,6 +3007,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2697
3007
  file,
2698
3008
  typeRegistry,
2699
3009
  visiting,
3010
+ checker.getTypeAtLocation(typeAliasDecl),
3011
+ diagnostics,
2700
3012
  extensionRegistry
2701
3013
  );
2702
3014
  }
@@ -2746,7 +3058,7 @@ function isNullishTypeNode(typeNode) {
2746
3058
  }
2747
3059
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
2748
3060
  }
2749
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
3061
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
2750
3062
  const map = /* @__PURE__ */ new Map();
2751
3063
  for (const member of members) {
2752
3064
  if (ts3.isPropertySignature(member)) {
@@ -2756,6 +3068,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
2756
3068
  file,
2757
3069
  typeRegistry,
2758
3070
  visiting,
3071
+ diagnostics,
3072
+ hostType,
2759
3073
  extensionRegistry
2760
3074
  );
2761
3075
  if (fieldNode) {
@@ -3008,760 +3322,44 @@ var init_program = __esm({
3008
3322
  });
3009
3323
 
3010
3324
  // src/validate/constraint-validator.ts
3011
- import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
3012
- function addContradiction(ctx, message, primary, related) {
3013
- ctx.diagnostics.push({
3014
- code: "CONTRADICTING_CONSTRAINTS",
3015
- message,
3016
- severity: "error",
3017
- primaryLocation: primary,
3018
- relatedLocations: [related]
3019
- });
3020
- }
3021
- function addTypeMismatch(ctx, message, primary) {
3022
- ctx.diagnostics.push({
3023
- code: "TYPE_MISMATCH",
3024
- message,
3025
- severity: "error",
3026
- primaryLocation: primary,
3027
- relatedLocations: []
3028
- });
3029
- }
3030
- function addUnknownExtension(ctx, message, primary) {
3031
- ctx.diagnostics.push({
3032
- code: "UNKNOWN_EXTENSION",
3033
- message,
3034
- severity: "warning",
3035
- primaryLocation: primary,
3036
- relatedLocations: []
3037
- });
3038
- }
3039
- function addUnknownPathTarget(ctx, message, primary) {
3040
- ctx.diagnostics.push({
3041
- code: "UNKNOWN_PATH_TARGET",
3042
- message,
3043
- severity: "error",
3044
- primaryLocation: primary,
3045
- relatedLocations: []
3046
- });
3047
- }
3048
- function addConstraintBroadening(ctx, message, primary, related) {
3049
- ctx.diagnostics.push({
3050
- code: "CONSTRAINT_BROADENING",
3051
- message,
3052
- severity: "error",
3053
- primaryLocation: primary,
3054
- relatedLocations: [related]
3055
- });
3056
- }
3057
- function getExtensionIdFromConstraintId(constraintId) {
3058
- const separator = constraintId.lastIndexOf("/");
3059
- if (separator <= 0) {
3060
- return null;
3061
- }
3062
- return constraintId.slice(0, separator);
3063
- }
3064
- function findNumeric(constraints, constraintKind) {
3065
- return constraints.find((c) => c.constraintKind === constraintKind);
3066
- }
3067
- function findLength(constraints, constraintKind) {
3068
- return constraints.find((c) => c.constraintKind === constraintKind);
3069
- }
3070
- function findAllowedMembers(constraints) {
3071
- return constraints.filter(
3072
- (c) => c.constraintKind === "allowedMembers"
3073
- );
3074
- }
3075
- function findConstConstraints(constraints) {
3076
- return constraints.filter(
3077
- (c) => c.constraintKind === "const"
3078
- );
3079
- }
3080
- function jsonValueEquals(left, right) {
3081
- if (left === right) {
3082
- return true;
3083
- }
3084
- if (Array.isArray(left) || Array.isArray(right)) {
3085
- if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
3086
- return false;
3087
- }
3088
- return left.every((item, index) => jsonValueEquals(item, right[index]));
3089
- }
3090
- if (isJsonObject(left) || isJsonObject(right)) {
3091
- if (!isJsonObject(left) || !isJsonObject(right)) {
3092
- return false;
3093
- }
3094
- const leftKeys = Object.keys(left).sort();
3095
- const rightKeys = Object.keys(right).sort();
3096
- if (leftKeys.length !== rightKeys.length) {
3097
- return false;
3098
- }
3099
- return leftKeys.every((key, index) => {
3100
- const rightKey = rightKeys[index];
3101
- if (rightKey !== key) {
3102
- return false;
3103
- }
3104
- const leftValue = left[key];
3105
- const rightValue = right[rightKey];
3106
- return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
3107
- });
3108
- }
3109
- return false;
3110
- }
3111
- function isJsonObject(value) {
3112
- return typeof value === "object" && value !== null && !Array.isArray(value);
3113
- }
3114
- function isOrderedBoundConstraint(constraint) {
3115
- 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";
3116
- }
3117
- function pathKey(constraint) {
3118
- return constraint.path?.segments.join(".") ?? "";
3119
- }
3120
- function orderedBoundFamily(kind) {
3121
- switch (kind) {
3122
- case "minimum":
3123
- case "exclusiveMinimum":
3124
- return "numeric-lower";
3125
- case "maximum":
3126
- case "exclusiveMaximum":
3127
- return "numeric-upper";
3128
- case "minLength":
3129
- return "minLength";
3130
- case "minItems":
3131
- return "minItems";
3132
- case "maxLength":
3133
- return "maxLength";
3134
- case "maxItems":
3135
- return "maxItems";
3136
- default: {
3137
- const _exhaustive = kind;
3138
- return _exhaustive;
3139
- }
3140
- }
3141
- }
3142
- function isNumericLowerKind(kind) {
3143
- return kind === "minimum" || kind === "exclusiveMinimum";
3144
- }
3145
- function isNumericUpperKind(kind) {
3146
- return kind === "maximum" || kind === "exclusiveMaximum";
3147
- }
3148
- function describeConstraintTag(constraint) {
3149
- return `@${constraint.constraintKind}`;
3150
- }
3151
- function compareConstraintStrength(current, previous) {
3152
- const family = orderedBoundFamily(current.constraintKind);
3153
- if (family === "numeric-lower") {
3154
- if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
3155
- throw new Error("numeric-lower family received non-numeric lower-bound constraint");
3156
- }
3157
- if (current.value !== previous.value) {
3158
- return current.value > previous.value ? 1 : -1;
3159
- }
3160
- if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
3161
- return 1;
3162
- }
3163
- if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
3164
- return -1;
3165
- }
3166
- return 0;
3167
- }
3168
- if (family === "numeric-upper") {
3169
- if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
3170
- throw new Error("numeric-upper family received non-numeric upper-bound constraint");
3171
- }
3172
- if (current.value !== previous.value) {
3173
- return current.value < previous.value ? 1 : -1;
3174
- }
3175
- if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
3176
- return 1;
3177
- }
3178
- if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
3179
- return -1;
3180
- }
3181
- return 0;
3182
- }
3183
- switch (family) {
3184
- case "minLength":
3185
- case "minItems":
3186
- if (current.value === previous.value) {
3187
- return 0;
3188
- }
3189
- return current.value > previous.value ? 1 : -1;
3190
- case "maxLength":
3191
- case "maxItems":
3192
- if (current.value === previous.value) {
3193
- return 0;
3194
- }
3195
- return current.value < previous.value ? 1 : -1;
3196
- default: {
3197
- const _exhaustive = family;
3198
- return _exhaustive;
3199
- }
3200
- }
3201
- }
3202
- function checkConstraintBroadening(ctx, fieldName, constraints) {
3203
- const strongestByKey = /* @__PURE__ */ new Map();
3204
- for (const constraint of constraints) {
3205
- if (!isOrderedBoundConstraint(constraint)) {
3206
- continue;
3207
- }
3208
- const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
3209
- const previous = strongestByKey.get(key);
3210
- if (previous === void 0) {
3211
- strongestByKey.set(key, constraint);
3212
- continue;
3213
- }
3214
- const strength = compareConstraintStrength(constraint, previous);
3215
- if (strength < 0) {
3216
- const displayFieldName = formatPathTargetFieldName(
3217
- fieldName,
3218
- constraint.path?.segments ?? []
3219
- );
3220
- addConstraintBroadening(
3221
- ctx,
3222
- `Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
3223
- constraint.provenance,
3224
- previous.provenance
3225
- );
3226
- continue;
3227
- }
3228
- if (strength <= 0) {
3229
- continue;
3230
- }
3231
- strongestByKey.set(key, constraint);
3232
- }
3233
- }
3234
- function compareCustomConstraintStrength(current, previous) {
3235
- const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
3236
- const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
3237
- switch (current.role.bound) {
3238
- case "lower":
3239
- return equalPayloadTiebreaker;
3240
- case "upper":
3241
- return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
3242
- case "exact":
3243
- return order === 0 ? 0 : Number.NaN;
3244
- default: {
3245
- const _exhaustive = current.role.bound;
3246
- return _exhaustive;
3247
- }
3248
- }
3249
- }
3250
- function compareSemanticInclusivity(currentInclusive, previousInclusive) {
3251
- if (currentInclusive === previousInclusive) {
3252
- return 0;
3253
- }
3254
- return currentInclusive ? -1 : 1;
3255
- }
3256
- function customConstraintsContradict(lower, upper) {
3257
- const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
3258
- if (order > 0) {
3259
- return true;
3260
- }
3261
- if (order < 0) {
3262
- return false;
3263
- }
3264
- return !lower.role.inclusive || !upper.role.inclusive;
3265
- }
3266
- function describeCustomConstraintTag(constraint) {
3267
- return constraint.provenance.tagName ?? constraint.constraintId;
3268
- }
3269
- function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
3270
- if (ctx.extensionRegistry === void 0) {
3271
- return;
3272
- }
3273
- const strongestByKey = /* @__PURE__ */ new Map();
3274
- const lowerByFamily = /* @__PURE__ */ new Map();
3275
- const upperByFamily = /* @__PURE__ */ new Map();
3276
- for (const constraint of constraints) {
3277
- if (constraint.constraintKind !== "custom") {
3278
- continue;
3279
- }
3280
- const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3281
- if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
3282
- continue;
3283
- }
3284
- const entry = {
3285
- constraint,
3286
- comparePayloads: registration.comparePayloads,
3287
- role: registration.semanticRole
3288
- };
3289
- const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
3290
- const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
3291
- const previous = strongestByKey.get(boundKey);
3292
- if (previous !== void 0) {
3293
- const strength = compareCustomConstraintStrength(entry, previous);
3294
- if (Number.isNaN(strength)) {
3295
- addContradiction(
3296
- ctx,
3297
- `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
3298
- constraint.provenance,
3299
- previous.constraint.provenance
3300
- );
3301
- continue;
3302
- }
3303
- if (strength < 0) {
3304
- addConstraintBroadening(
3305
- ctx,
3306
- `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
3307
- constraint.provenance,
3308
- previous.constraint.provenance
3309
- );
3310
- continue;
3311
- }
3312
- if (strength > 0) {
3313
- strongestByKey.set(boundKey, entry);
3314
- }
3315
- } else {
3316
- strongestByKey.set(boundKey, entry);
3317
- }
3318
- if (registration.semanticRole.bound === "lower") {
3319
- lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3320
- } else if (registration.semanticRole.bound === "upper") {
3321
- upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3322
- }
3323
- }
3324
- for (const [familyKey, lower] of lowerByFamily) {
3325
- const upper = upperByFamily.get(familyKey);
3326
- if (upper === void 0) {
3327
- continue;
3328
- }
3329
- if (!customConstraintsContradict(lower, upper)) {
3330
- continue;
3331
- }
3332
- addContradiction(
3333
- ctx,
3334
- `Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
3335
- lower.constraint.provenance,
3336
- upper.constraint.provenance
3337
- );
3338
- }
3339
- }
3340
- function checkNumericContradictions(ctx, fieldName, constraints) {
3341
- const min = findNumeric(constraints, "minimum");
3342
- const max = findNumeric(constraints, "maximum");
3343
- const exMin = findNumeric(constraints, "exclusiveMinimum");
3344
- const exMax = findNumeric(constraints, "exclusiveMaximum");
3345
- if (min !== void 0 && max !== void 0 && min.value > max.value) {
3346
- addContradiction(
3347
- ctx,
3348
- `Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
3349
- min.provenance,
3350
- max.provenance
3351
- );
3352
- }
3353
- if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
3354
- addContradiction(
3355
- ctx,
3356
- `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
3357
- exMin.provenance,
3358
- max.provenance
3359
- );
3360
- }
3361
- if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
3362
- addContradiction(
3363
- ctx,
3364
- `Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3365
- min.provenance,
3366
- exMax.provenance
3367
- );
3368
- }
3369
- if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
3370
- addContradiction(
3371
- ctx,
3372
- `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3373
- exMin.provenance,
3374
- exMax.provenance
3375
- );
3376
- }
3377
- }
3378
- function checkLengthContradictions(ctx, fieldName, constraints) {
3379
- const minLen = findLength(constraints, "minLength");
3380
- const maxLen = findLength(constraints, "maxLength");
3381
- if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
3382
- addContradiction(
3383
- ctx,
3384
- `Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
3385
- minLen.provenance,
3386
- maxLen.provenance
3387
- );
3388
- }
3389
- const minItems = findLength(constraints, "minItems");
3390
- const maxItems = findLength(constraints, "maxItems");
3391
- if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
3392
- addContradiction(
3393
- ctx,
3394
- `Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
3395
- minItems.provenance,
3396
- maxItems.provenance
3397
- );
3398
- }
3399
- }
3400
- function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
3401
- const members = findAllowedMembers(constraints);
3402
- if (members.length < 2) return;
3403
- const firstSet = new Set(members[0]?.members ?? []);
3404
- for (let i = 1; i < members.length; i++) {
3405
- const current = members[i];
3406
- if (current === void 0) continue;
3407
- for (const m of firstSet) {
3408
- if (!current.members.includes(m)) {
3409
- firstSet.delete(m);
3410
- }
3411
- }
3412
- }
3413
- if (firstSet.size === 0) {
3414
- const first = members[0];
3415
- const second = members[1];
3416
- if (first !== void 0 && second !== void 0) {
3417
- addContradiction(
3418
- ctx,
3419
- `Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
3420
- first.provenance,
3421
- second.provenance
3422
- );
3423
- }
3424
- }
3425
- }
3426
- function checkConstContradictions(ctx, fieldName, constraints) {
3427
- const constConstraints = findConstConstraints(constraints);
3428
- if (constConstraints.length < 2) return;
3429
- const first = constConstraints[0];
3430
- if (first === void 0) return;
3431
- for (let i = 1; i < constConstraints.length; i++) {
3432
- const current = constConstraints[i];
3433
- if (current === void 0) continue;
3434
- if (jsonValueEquals(first.value, current.value)) {
3435
- continue;
3436
- }
3437
- addContradiction(
3438
- ctx,
3439
- `Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
3440
- first.provenance,
3441
- current.provenance
3442
- );
3443
- }
3444
- }
3445
- function typeLabel(type) {
3446
- switch (type.kind) {
3447
- case "primitive":
3448
- return type.primitiveKind;
3449
- case "enum":
3450
- return "enum";
3451
- case "array":
3452
- return "array";
3453
- case "object":
3454
- return "object";
3455
- case "record":
3456
- return "record";
3457
- case "union":
3458
- return "union";
3459
- case "reference":
3460
- return `reference(${type.name})`;
3461
- case "dynamic":
3462
- return `dynamic(${type.dynamicKind})`;
3463
- case "custom":
3464
- return `custom(${type.typeId})`;
3465
- default: {
3466
- const _exhaustive = type;
3467
- return String(_exhaustive);
3468
- }
3469
- }
3470
- }
3471
- function dereferenceType(ctx, type) {
3472
- let current = type;
3473
- const seen = /* @__PURE__ */ new Set();
3474
- while (current.kind === "reference") {
3475
- if (seen.has(current.name)) {
3476
- return current;
3477
- }
3478
- seen.add(current.name);
3479
- const definition = ctx.typeRegistry[current.name];
3480
- if (definition === void 0) {
3481
- return current;
3482
- }
3483
- current = definition.type;
3484
- }
3485
- return current;
3486
- }
3487
- function collectReferencedTypeConstraints(ctx, type) {
3488
- const collected = [];
3489
- let current = type;
3490
- const seen = /* @__PURE__ */ new Set();
3491
- while (current.kind === "reference") {
3492
- if (seen.has(current.name)) {
3493
- break;
3494
- }
3495
- seen.add(current.name);
3496
- const definition = ctx.typeRegistry[current.name];
3497
- if (definition === void 0) {
3498
- break;
3499
- }
3500
- if (definition.constraints !== void 0) {
3501
- collected.push(...definition.constraints);
3502
- }
3503
- current = definition.type;
3504
- }
3505
- return collected;
3506
- }
3507
- function resolvePathTargetType(ctx, type, segments) {
3508
- const effectiveType = dereferenceType(ctx, type);
3509
- if (segments.length === 0) {
3510
- return { kind: "resolved", type: effectiveType };
3511
- }
3512
- if (effectiveType.kind === "array") {
3513
- return resolvePathTargetType(ctx, effectiveType.items, segments);
3514
- }
3515
- if (effectiveType.kind === "object") {
3516
- const [segment, ...rest] = segments;
3517
- if (segment === void 0) {
3518
- throw new Error("Invariant violation: object path traversal requires a segment");
3519
- }
3520
- const property = effectiveType.properties.find((prop) => prop.name === segment);
3521
- if (property === void 0) {
3522
- return { kind: "missing-property", segment };
3523
- }
3524
- return resolvePathTargetType(ctx, property.type, rest);
3525
- }
3526
- return { kind: "unresolvable", type: effectiveType };
3527
- }
3528
- function isNullType(type) {
3529
- return type.kind === "primitive" && type.primitiveKind === "null";
3530
- }
3531
- function collectCustomConstraintCandidateTypes(ctx, type) {
3532
- const effectiveType = dereferenceType(ctx, type);
3533
- const candidates = [effectiveType];
3534
- if (effectiveType.kind === "array") {
3535
- candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
3536
- }
3537
- if (effectiveType.kind === "union") {
3538
- const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
3539
- const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
3540
- if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
3541
- const [nullableMember] = nonNullMembers;
3542
- if (nullableMember !== void 0) {
3543
- candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
3544
- }
3545
- }
3546
- }
3547
- return candidates;
3548
- }
3549
- function formatPathTargetFieldName(fieldName, path4) {
3550
- return path4.length === 0 ? fieldName : `${fieldName}.${path4.join(".")}`;
3551
- }
3552
- function checkConstraintOnType(ctx, fieldName, type, constraint) {
3553
- const effectiveType = dereferenceType(ctx, type);
3554
- const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
3555
- const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
3556
- const isArray = effectiveType.kind === "array";
3557
- const isEnum = effectiveType.kind === "enum";
3558
- const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
3559
- const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
3560
- const label = typeLabel(effectiveType);
3561
- const ck = constraint.constraintKind;
3562
- switch (ck) {
3563
- case "minimum":
3564
- case "maximum":
3565
- case "exclusiveMinimum":
3566
- case "exclusiveMaximum":
3567
- case "multipleOf": {
3568
- if (!isNumber) {
3569
- addTypeMismatch(
3570
- ctx,
3571
- `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
3572
- constraint.provenance
3573
- );
3574
- }
3575
- break;
3576
- }
3577
- case "minLength":
3578
- case "maxLength":
3579
- case "pattern": {
3580
- if (!isString && !isStringArray) {
3581
- addTypeMismatch(
3582
- ctx,
3583
- `Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
3584
- constraint.provenance
3585
- );
3586
- }
3587
- break;
3588
- }
3589
- case "minItems":
3590
- case "maxItems":
3591
- case "uniqueItems": {
3592
- if (!isArray) {
3593
- addTypeMismatch(
3594
- ctx,
3595
- `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
3596
- constraint.provenance
3597
- );
3598
- }
3599
- break;
3600
- }
3601
- case "allowedMembers": {
3602
- if (!isEnum) {
3603
- addTypeMismatch(
3604
- ctx,
3605
- `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
3606
- constraint.provenance
3607
- );
3608
- }
3609
- break;
3610
- }
3611
- case "const": {
3612
- const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
3613
- effectiveType.primitiveKind
3614
- ) || effectiveType.kind === "enum";
3615
- if (!isPrimitiveConstType) {
3616
- addTypeMismatch(
3617
- ctx,
3618
- `Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
3619
- constraint.provenance
3620
- );
3621
- break;
3622
- }
3623
- if (effectiveType.kind === "primitive") {
3624
- const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
3625
- const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
3626
- if (valueType !== expectedValueType) {
3627
- addTypeMismatch(
3628
- ctx,
3629
- `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
3630
- constraint.provenance
3631
- );
3632
- }
3633
- break;
3634
- }
3635
- const memberValues = effectiveType.members.map((member) => member.value);
3636
- if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
3637
- addTypeMismatch(
3638
- ctx,
3639
- `Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
3640
- constraint.provenance
3641
- );
3642
- }
3643
- break;
3644
- }
3645
- case "custom": {
3646
- checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
3647
- break;
3648
- }
3649
- default: {
3650
- const _exhaustive = constraint;
3651
- throw new Error(
3652
- `Unhandled constraint kind: ${_exhaustive.constraintKind}`
3653
- );
3654
- }
3655
- }
3656
- }
3657
- function checkTypeApplicability(ctx, fieldName, type, constraints) {
3658
- for (const constraint of constraints) {
3659
- if (constraint.path) {
3660
- const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
3661
- const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
3662
- if (resolution.kind === "missing-property") {
3663
- addUnknownPathTarget(
3664
- ctx,
3665
- `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
3666
- constraint.provenance
3667
- );
3668
- continue;
3669
- }
3670
- if (resolution.kind === "unresolvable") {
3671
- addTypeMismatch(
3672
- ctx,
3673
- `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
3674
- constraint.provenance
3675
- );
3676
- continue;
3677
- }
3678
- checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
3679
- continue;
3680
- }
3681
- checkConstraintOnType(ctx, fieldName, type, constraint);
3682
- }
3683
- }
3684
- function checkCustomConstraint(ctx, fieldName, type, constraint) {
3685
- if (ctx.extensionRegistry === void 0) return;
3686
- const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3687
- if (registration === void 0) {
3688
- addUnknownExtension(
3689
- ctx,
3690
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
3691
- constraint.provenance
3692
- );
3693
- return;
3694
- }
3695
- const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
3696
- const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
3697
- if (normalizedTagName !== void 0) {
3698
- const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
3699
- const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
3700
- if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
3701
- (candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
3702
- )) {
3703
- addTypeMismatch(
3704
- ctx,
3705
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3706
- constraint.provenance
3707
- );
3708
- return;
3709
- }
3710
- }
3711
- if (registration.applicableTypes === null) {
3712
- if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
3713
- addTypeMismatch(
3714
- ctx,
3715
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3716
- constraint.provenance
3717
- );
3325
+ import {
3326
+ analyzeConstraintTargets
3327
+ } from "@formspec/analysis";
3328
+ function validateFieldNode(ctx, field) {
3329
+ const analysis = analyzeConstraintTargets(
3330
+ field.name,
3331
+ field.type,
3332
+ field.constraints,
3333
+ ctx.typeRegistry,
3334
+ ctx.extensionRegistry === void 0 ? void 0 : {
3335
+ extensionRegistry: ctx.extensionRegistry
3718
3336
  }
3719
- return;
3720
- }
3721
- const applicableTypes = registration.applicableTypes;
3722
- const matchesApplicableType = candidateTypes.some(
3723
- (candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
3724
3337
  );
3725
- if (!matchesApplicableType) {
3726
- addTypeMismatch(
3727
- ctx,
3728
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3729
- constraint.provenance
3730
- );
3731
- }
3732
- }
3733
- function validateFieldNode(ctx, field) {
3734
- validateConstraints(ctx, field.name, field.type, [
3735
- ...collectReferencedTypeConstraints(ctx, field.type),
3736
- ...field.constraints
3737
- ]);
3338
+ ctx.diagnostics.push(...analysis.diagnostics);
3738
3339
  if (field.type.kind === "object") {
3739
- for (const prop of field.type.properties) {
3740
- validateObjectProperty(ctx, field.name, prop);
3340
+ for (const property of field.type.properties) {
3341
+ validateObjectProperty(ctx, field.name, property);
3741
3342
  }
3742
3343
  }
3743
3344
  }
3744
- function validateObjectProperty(ctx, parentName, prop) {
3745
- const qualifiedName = `${parentName}.${prop.name}`;
3746
- validateConstraints(ctx, qualifiedName, prop.type, [
3747
- ...collectReferencedTypeConstraints(ctx, prop.type),
3748
- ...prop.constraints
3749
- ]);
3750
- if (prop.type.kind === "object") {
3751
- for (const nestedProp of prop.type.properties) {
3752
- validateObjectProperty(ctx, qualifiedName, nestedProp);
3345
+ function validateObjectProperty(ctx, parentName, property) {
3346
+ const qualifiedName = `${parentName}.${property.name}`;
3347
+ const analysis = analyzeConstraintTargets(
3348
+ qualifiedName,
3349
+ property.type,
3350
+ property.constraints,
3351
+ ctx.typeRegistry,
3352
+ ctx.extensionRegistry === void 0 ? void 0 : {
3353
+ extensionRegistry: ctx.extensionRegistry
3354
+ }
3355
+ );
3356
+ ctx.diagnostics.push(...analysis.diagnostics);
3357
+ if (property.type.kind === "object") {
3358
+ for (const nestedProperty of property.type.properties) {
3359
+ validateObjectProperty(ctx, qualifiedName, nestedProperty);
3753
3360
  }
3754
3361
  }
3755
3362
  }
3756
- function validateConstraints(ctx, name, type, constraints) {
3757
- checkNumericContradictions(ctx, name, constraints);
3758
- checkLengthContradictions(ctx, name, constraints);
3759
- checkAllowedMembersContradiction(ctx, name, constraints);
3760
- checkConstContradictions(ctx, name, constraints);
3761
- checkConstraintBroadening(ctx, name, constraints);
3762
- checkCustomConstraintSemantics(ctx, name, constraints);
3763
- checkTypeApplicability(ctx, name, type, constraints);
3764
- }
3765
3363
  function validateElement(ctx, element) {
3766
3364
  switch (element.kind) {
3767
3365
  case "field":
@@ -3778,8 +3376,8 @@ function validateElement(ctx, element) {
3778
3376
  }
3779
3377
  break;
3780
3378
  default: {
3781
- const _exhaustive = element;
3782
- throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
3379
+ const exhaustive = element;
3380
+ throw new Error(`Unhandled element kind: ${String(exhaustive)}`);
3783
3381
  }
3784
3382
  }
3785
3383
  }
@@ -3794,7 +3392,7 @@ function validateIR(ir, options) {
3794
3392
  }
3795
3393
  return {
3796
3394
  diagnostics: ctx.diagnostics,
3797
- valid: ctx.diagnostics.every((d) => d.severity !== "error")
3395
+ valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
3798
3396
  };
3799
3397
  }
3800
3398
  var init_constraint_validator = __esm({
@@ -3813,6 +3411,12 @@ var init_validate = __esm({
3813
3411
 
3814
3412
  // src/generators/class-schema.ts
3815
3413
  function generateClassSchemas(analysis, source, options) {
3414
+ const errorDiagnostics = analysis.diagnostics?.filter(
3415
+ (diagnostic) => diagnostic.severity === "error"
3416
+ );
3417
+ if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3418
+ throw new Error(formatValidationError(errorDiagnostics));
3419
+ }
3816
3420
  const ir = canonicalizeTSDoc(analysis, source);
3817
3421
  const validationResult = validateIR(ir, {
3818
3422
  ...options?.extensionRegistry !== void 0 && {