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