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