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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
  2. package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
  3. package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
  4. package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
  5. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +31 -0
  6. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
  7. package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
  8. package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
  9. package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
  10. package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
  11. package/dist/__tests__/parity/utils.d.ts +5 -3
  12. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  13. package/dist/analyzer/class-analyzer.d.ts +8 -5
  14. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  15. package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
  16. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  17. package/dist/analyzer/tsdoc-parser.d.ts +38 -4
  18. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  19. package/dist/browser.cjs +371 -21
  20. package/dist/browser.cjs.map +1 -1
  21. package/dist/browser.d.ts.map +1 -1
  22. package/dist/browser.js +371 -21
  23. package/dist/browser.js.map +1 -1
  24. package/dist/build.d.ts +67 -3
  25. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  26. package/dist/cli.cjs +1159 -150
  27. package/dist/cli.cjs.map +1 -1
  28. package/dist/cli.js +1159 -150
  29. package/dist/cli.js.map +1 -1
  30. package/dist/extensions/registry.d.ts +25 -1
  31. package/dist/extensions/registry.d.ts.map +1 -1
  32. package/dist/generators/class-schema.d.ts +4 -4
  33. package/dist/generators/class-schema.d.ts.map +1 -1
  34. package/dist/generators/mixed-authoring.d.ts +45 -0
  35. package/dist/generators/mixed-authoring.d.ts.map +1 -0
  36. package/dist/index.cjs +1146 -149
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1145 -149
  41. package/dist/index.js.map +1 -1
  42. package/dist/internals.cjs +1156 -149
  43. package/dist/internals.cjs.map +1 -1
  44. package/dist/internals.js +1154 -147
  45. package/dist/internals.js.map +1 -1
  46. package/dist/json-schema/ir-generator.d.ts +3 -2
  47. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  48. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  49. package/dist/validate/constraint-validator.d.ts.map +1 -1
  50. package/package.json +3 -3
  51. package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -9
  52. package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
package/dist/cli.js CHANGED
@@ -342,6 +342,7 @@ function canonicalizeTSDoc(analysis, source) {
342
342
  irVersion: IR_VERSION2,
343
343
  elements,
344
344
  typeRegistry: analysis.typeRegistry,
345
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
345
346
  provenance
346
347
  };
347
348
  }
@@ -432,6 +433,9 @@ function generateJsonSchemaFromIR(ir, options) {
432
433
  const ctx = makeContext(options);
433
434
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
434
435
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
436
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
437
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
438
+ }
435
439
  }
436
440
  const properties = {};
437
441
  const required = [];
@@ -443,6 +447,9 @@ function generateJsonSchemaFromIR(ir, options) {
443
447
  properties,
444
448
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
445
449
  };
450
+ if (ir.annotations && ir.annotations.length > 0) {
451
+ applyAnnotations(result, ir.annotations, ctx);
452
+ }
446
453
  if (Object.keys(ctx.defs).length > 0) {
447
454
  result.$defs = ctx.defs;
448
455
  }
@@ -472,22 +479,51 @@ function collectFields(elements, properties, required, ctx) {
472
479
  }
473
480
  function generateFieldSchema(field, ctx) {
474
481
  const schema = generateTypeNode(field.type, ctx);
482
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
475
483
  const directConstraints = [];
484
+ const itemConstraints = [];
476
485
  const pathConstraints = [];
477
486
  for (const c of field.constraints) {
478
487
  if (c.path) {
479
488
  pathConstraints.push(c);
489
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
490
+ itemConstraints.push(c);
480
491
  } else {
481
492
  directConstraints.push(c);
482
493
  }
483
494
  }
484
495
  applyConstraints(schema, directConstraints, ctx);
485
- applyAnnotations(schema, field.annotations, ctx);
496
+ if (itemStringSchema !== void 0) {
497
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
498
+ }
499
+ const rootAnnotations = [];
500
+ const itemAnnotations = [];
501
+ for (const annotation of field.annotations) {
502
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
503
+ itemAnnotations.push(annotation);
504
+ } else {
505
+ rootAnnotations.push(annotation);
506
+ }
507
+ }
508
+ applyAnnotations(schema, rootAnnotations, ctx);
509
+ if (itemStringSchema !== void 0) {
510
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
511
+ }
486
512
  if (pathConstraints.length === 0) {
487
513
  return schema;
488
514
  }
489
515
  return applyPathTargetedConstraints(schema, pathConstraints, ctx);
490
516
  }
517
+ function isStringItemConstraint(constraint) {
518
+ switch (constraint.constraintKind) {
519
+ case "minLength":
520
+ case "maxLength":
521
+ case "pattern":
522
+ return true;
523
+ default:
524
+ return false;
525
+ }
526
+ }
491
527
  function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
492
528
  if (schema.type === "array" && schema.items) {
493
529
  schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
@@ -705,6 +741,9 @@ function applyConstraints(schema, constraints, ctx) {
705
741
  case "uniqueItems":
706
742
  schema.uniqueItems = constraint.value;
707
743
  break;
744
+ case "const":
745
+ schema.const = constraint.value;
746
+ break;
708
747
  case "allowedMembers":
709
748
  break;
710
749
  case "custom":
@@ -729,8 +768,14 @@ function applyAnnotations(schema, annotations, ctx) {
729
768
  case "defaultValue":
730
769
  schema.default = annotation.value;
731
770
  break;
771
+ case "format":
772
+ schema.format = annotation.value;
773
+ break;
732
774
  case "deprecated":
733
775
  schema.deprecated = true;
776
+ if (annotation.message !== void 0 && annotation.message !== "") {
777
+ schema["x-formspec-deprecation-description"] = annotation.message;
778
+ }
734
779
  break;
735
780
  case "placeholder":
736
781
  break;
@@ -762,7 +807,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
762
807
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
763
808
  );
764
809
  }
765
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
810
+ assignVendorPrefixedExtensionKeywords(
811
+ schema,
812
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
813
+ ctx.vendorPrefix,
814
+ `custom constraint "${constraint.constraintId}"`
815
+ );
766
816
  }
767
817
  function applyCustomAnnotation(schema, annotation, ctx) {
768
818
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -774,7 +824,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
774
824
  if (registration.toJsonSchema === void 0) {
775
825
  return;
776
826
  }
777
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
827
+ assignVendorPrefixedExtensionKeywords(
828
+ schema,
829
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
830
+ ctx.vendorPrefix,
831
+ `custom annotation "${annotation.annotationId}"`
832
+ );
833
+ }
834
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
835
+ for (const [key, value] of Object.entries(extensionSchema)) {
836
+ if (!key.startsWith(`${vendorPrefix}-`)) {
837
+ throw new Error(
838
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
839
+ );
840
+ }
841
+ schema[key] = value;
842
+ }
778
843
  }
779
844
  var init_ir_generator = __esm({
780
845
  "src/json-schema/ir-generator.ts"() {
@@ -936,25 +1001,31 @@ function createShowRule(fieldName, value) {
936
1001
  }
937
1002
  };
938
1003
  }
1004
+ function flattenConditionSchema(scope, schema) {
1005
+ if (schema.allOf === void 0) {
1006
+ if (scope === "#") {
1007
+ return [schema];
1008
+ }
1009
+ const fieldName = scope.replace("#/properties/", "");
1010
+ return [
1011
+ {
1012
+ properties: {
1013
+ [fieldName]: schema
1014
+ }
1015
+ }
1016
+ ];
1017
+ }
1018
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
1019
+ }
939
1020
  function combineRules(parentRule, childRule) {
940
- const parentCondition = parentRule.condition;
941
- const childCondition = childRule.condition;
942
1021
  return {
943
1022
  effect: "SHOW",
944
1023
  condition: {
945
1024
  scope: "#",
946
1025
  schema: {
947
1026
  allOf: [
948
- {
949
- properties: {
950
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
951
- }
952
- },
953
- {
954
- properties: {
955
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
956
- }
957
- }
1027
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
1028
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
958
1029
  ]
959
1030
  }
960
1031
  }
@@ -962,10 +1033,14 @@ function combineRules(parentRule, childRule) {
962
1033
  }
963
1034
  function fieldNodeToControl(field, parentRule) {
964
1035
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1036
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
965
1037
  const control = {
966
1038
  type: "Control",
967
1039
  scope: fieldToScope(field.name),
968
1040
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1041
+ ...placeholderAnnotation !== void 0 && {
1042
+ options: { placeholder: placeholderAnnotation.value }
1043
+ },
969
1044
  ...parentRule !== void 0 && { rule: parentRule }
970
1045
  };
971
1046
  return control;
@@ -1049,7 +1124,10 @@ var init_types = __esm({
1049
1124
  // src/extensions/registry.ts
1050
1125
  function createExtensionRegistry(extensions) {
1051
1126
  const typeMap = /* @__PURE__ */ new Map();
1127
+ const typeNameMap = /* @__PURE__ */ new Map();
1052
1128
  const constraintMap = /* @__PURE__ */ new Map();
1129
+ const constraintTagMap = /* @__PURE__ */ new Map();
1130
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
1053
1131
  const annotationMap = /* @__PURE__ */ new Map();
1054
1132
  for (const ext of extensions) {
1055
1133
  if (ext.types !== void 0) {
@@ -1059,6 +1137,27 @@ function createExtensionRegistry(extensions) {
1059
1137
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1060
1138
  }
1061
1139
  typeMap.set(qualifiedId, type);
1140
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
1141
+ if (typeNameMap.has(sourceTypeName)) {
1142
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
1143
+ }
1144
+ typeNameMap.set(sourceTypeName, {
1145
+ extensionId: ext.extensionId,
1146
+ registration: type
1147
+ });
1148
+ }
1149
+ if (type.builtinConstraintBroadenings !== void 0) {
1150
+ for (const broadening of type.builtinConstraintBroadenings) {
1151
+ const key = `${qualifiedId}:${broadening.tagName}`;
1152
+ if (builtinBroadeningMap.has(key)) {
1153
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
1154
+ }
1155
+ builtinBroadeningMap.set(key, {
1156
+ extensionId: ext.extensionId,
1157
+ registration: broadening
1158
+ });
1159
+ }
1160
+ }
1062
1161
  }
1063
1162
  }
1064
1163
  if (ext.constraints !== void 0) {
@@ -1070,6 +1169,17 @@ function createExtensionRegistry(extensions) {
1070
1169
  constraintMap.set(qualifiedId, constraint);
1071
1170
  }
1072
1171
  }
1172
+ if (ext.constraintTags !== void 0) {
1173
+ for (const tag of ext.constraintTags) {
1174
+ if (constraintTagMap.has(tag.tagName)) {
1175
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
1176
+ }
1177
+ constraintTagMap.set(tag.tagName, {
1178
+ extensionId: ext.extensionId,
1179
+ registration: tag
1180
+ });
1181
+ }
1182
+ }
1073
1183
  if (ext.annotations !== void 0) {
1074
1184
  for (const annotation of ext.annotations) {
1075
1185
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -1083,7 +1193,10 @@ function createExtensionRegistry(extensions) {
1083
1193
  return {
1084
1194
  extensions,
1085
1195
  findType: (typeId) => typeMap.get(typeId),
1196
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
1086
1197
  findConstraint: (constraintId) => constraintMap.get(constraintId),
1198
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
1199
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
1087
1200
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
1088
1201
  };
1089
1202
  }
@@ -1276,7 +1389,7 @@ import {
1276
1389
  normalizeConstraintTagName,
1277
1390
  isBuiltinConstraintName
1278
1391
  } from "@formspec/core";
1279
- function createFormSpecTSDocConfig() {
1392
+ function createFormSpecTSDocConfig(extensionTagNames = []) {
1280
1393
  const config = new TSDocConfiguration();
1281
1394
  for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
1282
1395
  config.addTagDefinition(
@@ -1287,7 +1400,16 @@ function createFormSpecTSDocConfig() {
1287
1400
  })
1288
1401
  );
1289
1402
  }
1290
- for (const tagName of ["displayName", "description"]) {
1403
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1404
+ config.addTagDefinition(
1405
+ new TSDocTagDefinition({
1406
+ tagName: "@" + tagName,
1407
+ syntaxKind: TSDocTagSyntaxKind.BlockTag,
1408
+ allowMultiple: true
1409
+ })
1410
+ );
1411
+ }
1412
+ for (const tagName of extensionTagNames) {
1291
1413
  config.addTagDefinition(
1292
1414
  new TSDocTagDefinition({
1293
1415
  tagName: "@" + tagName,
@@ -1298,13 +1420,30 @@ function createFormSpecTSDocConfig() {
1298
1420
  }
1299
1421
  return config;
1300
1422
  }
1301
- function getParser() {
1302
- sharedParser ??= new TSDocParser(createFormSpecTSDocConfig());
1303
- return sharedParser;
1304
- }
1305
- function parseTSDocTags(node, file = "") {
1423
+ function getParser(options) {
1424
+ const extensionTagNames = [
1425
+ ...options?.extensionRegistry?.extensions.flatMap(
1426
+ (extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
1427
+ ) ?? []
1428
+ ].sort();
1429
+ const cacheKey = extensionTagNames.join("|");
1430
+ const existing = parserCache.get(cacheKey);
1431
+ if (existing) {
1432
+ return existing;
1433
+ }
1434
+ const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
1435
+ parserCache.set(cacheKey, parser);
1436
+ return parser;
1437
+ }
1438
+ function parseTSDocTags(node, file = "", options) {
1306
1439
  const constraints = [];
1307
1440
  const annotations = [];
1441
+ let displayName;
1442
+ let description;
1443
+ let placeholder;
1444
+ let displayNameProvenance;
1445
+ let descriptionProvenance;
1446
+ let placeholderProvenance;
1308
1447
  const sourceFile = node.getSourceFile();
1309
1448
  const sourceText = sourceFile.getFullText();
1310
1449
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1317,52 +1456,92 @@ function parseTSDocTags(node, file = "") {
1317
1456
  if (!commentText.startsWith("/**")) {
1318
1457
  continue;
1319
1458
  }
1320
- const parser = getParser();
1459
+ const parser = getParser(options);
1321
1460
  const parserContext = parser.parseRange(
1322
1461
  TextRange.fromStringRange(sourceText, range.pos, range.end)
1323
1462
  );
1324
1463
  const docComment = parserContext.docComment;
1325
1464
  for (const block of docComment.customBlocks) {
1326
1465
  const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
1327
- if (tagName === "displayName" || tagName === "description") {
1466
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1328
1467
  const text2 = extractBlockText(block).trim();
1329
1468
  if (text2 === "") continue;
1330
1469
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1331
1470
  if (tagName === "displayName") {
1471
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1472
+ displayName = text2;
1473
+ displayNameProvenance = provenance2;
1474
+ }
1475
+ } else if (tagName === "format") {
1332
1476
  annotations.push({
1333
1477
  kind: "annotation",
1334
- annotationKind: "displayName",
1478
+ annotationKind: "format",
1335
1479
  value: text2,
1336
1480
  provenance: provenance2
1337
1481
  });
1338
1482
  } else {
1339
- annotations.push({
1340
- kind: "annotation",
1341
- annotationKind: "description",
1342
- value: text2,
1343
- provenance: provenance2
1344
- });
1483
+ if (tagName === "description" && description === void 0) {
1484
+ description = text2;
1485
+ descriptionProvenance = provenance2;
1486
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1487
+ placeholder = text2;
1488
+ placeholderProvenance = provenance2;
1489
+ }
1345
1490
  }
1346
1491
  continue;
1347
1492
  }
1348
1493
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1349
1494
  const text = extractBlockText(block).trim();
1350
- if (text === "") continue;
1495
+ const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1496
+ if (text === "" && expectedType !== "boolean") continue;
1351
1497
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1352
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1498
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1353
1499
  if (constraintNode) {
1354
1500
  constraints.push(constraintNode);
1355
1501
  }
1356
1502
  }
1357
1503
  if (docComment.deprecatedBlock !== void 0) {
1504
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1358
1505
  annotations.push({
1359
1506
  kind: "annotation",
1360
1507
  annotationKind: "deprecated",
1508
+ ...message !== "" && { message },
1361
1509
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1362
1510
  });
1363
1511
  }
1512
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1513
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1514
+ if (remarks !== "") {
1515
+ description = remarks;
1516
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1517
+ }
1518
+ }
1364
1519
  }
1365
1520
  }
1521
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1522
+ annotations.push({
1523
+ kind: "annotation",
1524
+ annotationKind: "displayName",
1525
+ value: displayName,
1526
+ provenance: displayNameProvenance
1527
+ });
1528
+ }
1529
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1530
+ annotations.push({
1531
+ kind: "annotation",
1532
+ annotationKind: "description",
1533
+ value: description,
1534
+ provenance: descriptionProvenance
1535
+ });
1536
+ }
1537
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1538
+ annotations.push({
1539
+ kind: "annotation",
1540
+ annotationKind: "placeholder",
1541
+ value: placeholder,
1542
+ provenance: placeholderProvenance
1543
+ });
1544
+ }
1366
1545
  const jsDocTagsAll = ts2.getJSDocTags(node);
1367
1546
  for (const tag of jsDocTagsAll) {
1368
1547
  const tagName = normalizeConstraintTagName(tag.tagName.text);
@@ -1371,13 +1550,40 @@ function parseTSDocTags(node, file = "") {
1371
1550
  if (commentText === void 0 || commentText.trim() === "") continue;
1372
1551
  const text = commentText.trim();
1373
1552
  const provenance = provenanceForJSDocTag(tag, file);
1374
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1553
+ if (tagName === "defaultValue") {
1554
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1555
+ annotations.push(defaultValueNode);
1556
+ continue;
1557
+ }
1558
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1375
1559
  if (constraintNode) {
1376
1560
  constraints.push(constraintNode);
1377
1561
  }
1378
1562
  }
1379
1563
  return { constraints, annotations };
1380
1564
  }
1565
+ function extractDisplayNameMetadata(node) {
1566
+ let displayName;
1567
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1568
+ for (const tag of ts2.getJSDocTags(node)) {
1569
+ const tagName = normalizeConstraintTagName(tag.tagName.text);
1570
+ if (tagName !== "displayName") continue;
1571
+ const commentText = getTagCommentText(tag);
1572
+ if (commentText === void 0) continue;
1573
+ const text = commentText.trim();
1574
+ if (text === "") continue;
1575
+ const memberTarget = parseMemberTargetDisplayName(text);
1576
+ if (memberTarget) {
1577
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1578
+ continue;
1579
+ }
1580
+ displayName ??= text;
1581
+ }
1582
+ return {
1583
+ ...displayName !== void 0 && { displayName },
1584
+ memberDisplayNames
1585
+ };
1586
+ }
1381
1587
  function extractPathTarget(text) {
1382
1588
  const trimmed = text.trimStart();
1383
1589
  const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
@@ -1405,7 +1611,11 @@ function extractPlainText(node) {
1405
1611
  }
1406
1612
  return result;
1407
1613
  }
1408
- function parseConstraintValue(tagName, text, provenance) {
1614
+ function parseConstraintValue(tagName, text, provenance, options) {
1615
+ const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
1616
+ if (customConstraint) {
1617
+ return customConstraint;
1618
+ }
1409
1619
  if (!isBuiltinConstraintName(tagName)) {
1410
1620
  return null;
1411
1621
  }
@@ -1440,7 +1650,45 @@ function parseConstraintValue(tagName, text, provenance) {
1440
1650
  }
1441
1651
  return null;
1442
1652
  }
1653
+ if (expectedType === "boolean") {
1654
+ const trimmed = effectiveText.trim();
1655
+ if (trimmed !== "" && trimmed !== "true") {
1656
+ return null;
1657
+ }
1658
+ if (tagName === "uniqueItems") {
1659
+ return {
1660
+ kind: "constraint",
1661
+ constraintKind: "uniqueItems",
1662
+ value: true,
1663
+ ...path4 && { path: path4 },
1664
+ provenance
1665
+ };
1666
+ }
1667
+ return null;
1668
+ }
1443
1669
  if (expectedType === "json") {
1670
+ if (tagName === "const") {
1671
+ const trimmedText = effectiveText.trim();
1672
+ if (trimmedText === "") return null;
1673
+ try {
1674
+ const parsed2 = JSON.parse(trimmedText);
1675
+ return {
1676
+ kind: "constraint",
1677
+ constraintKind: "const",
1678
+ value: parsed2,
1679
+ ...path4 && { path: path4 },
1680
+ provenance
1681
+ };
1682
+ } catch {
1683
+ return {
1684
+ kind: "constraint",
1685
+ constraintKind: "const",
1686
+ value: trimmedText,
1687
+ ...path4 && { path: path4 },
1688
+ provenance
1689
+ };
1690
+ }
1691
+ }
1444
1692
  const parsed = tryParseJson(effectiveText);
1445
1693
  if (!Array.isArray(parsed)) {
1446
1694
  return null;
@@ -1472,6 +1720,111 @@ function parseConstraintValue(tagName, text, provenance) {
1472
1720
  provenance
1473
1721
  };
1474
1722
  }
1723
+ function parseExtensionConstraintValue(tagName, text, provenance, options) {
1724
+ const pathResult = extractPathTarget(text);
1725
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1726
+ const path4 = pathResult?.path;
1727
+ const registry = options?.extensionRegistry;
1728
+ if (registry === void 0) {
1729
+ return null;
1730
+ }
1731
+ const directTag = registry.findConstraintTag(tagName);
1732
+ if (directTag !== void 0) {
1733
+ return makeCustomConstraintNode(
1734
+ directTag.extensionId,
1735
+ directTag.registration.constraintName,
1736
+ directTag.registration.parseValue(effectiveText),
1737
+ provenance,
1738
+ path4,
1739
+ registry
1740
+ );
1741
+ }
1742
+ if (!isBuiltinConstraintName(tagName)) {
1743
+ return null;
1744
+ }
1745
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
1746
+ if (broadenedTypeId === void 0) {
1747
+ return null;
1748
+ }
1749
+ const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
1750
+ if (broadened === void 0) {
1751
+ return null;
1752
+ }
1753
+ return makeCustomConstraintNode(
1754
+ broadened.extensionId,
1755
+ broadened.registration.constraintName,
1756
+ broadened.registration.parseValue(effectiveText),
1757
+ provenance,
1758
+ path4,
1759
+ registry
1760
+ );
1761
+ }
1762
+ function getBroadenedCustomTypeId(fieldType) {
1763
+ if (fieldType?.kind === "custom") {
1764
+ return fieldType.typeId;
1765
+ }
1766
+ if (fieldType?.kind !== "union") {
1767
+ return void 0;
1768
+ }
1769
+ const customMembers = fieldType.members.filter(
1770
+ (member) => member.kind === "custom"
1771
+ );
1772
+ if (customMembers.length !== 1) {
1773
+ return void 0;
1774
+ }
1775
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
1776
+ const allOtherMembersAreNull = nonCustomMembers.every(
1777
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
1778
+ );
1779
+ const customMember = customMembers[0];
1780
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
1781
+ }
1782
+ function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path4, registry) {
1783
+ const constraintId = `${extensionId}/${constraintName}`;
1784
+ const registration = registry.findConstraint(constraintId);
1785
+ if (registration === void 0) {
1786
+ throw new Error(
1787
+ `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
1788
+ );
1789
+ }
1790
+ return {
1791
+ kind: "constraint",
1792
+ constraintKind: "custom",
1793
+ constraintId,
1794
+ payload,
1795
+ compositionRule: registration.compositionRule,
1796
+ ...path4 && { path: path4 },
1797
+ provenance
1798
+ };
1799
+ }
1800
+ function parseDefaultValueValue(text, provenance) {
1801
+ const trimmed = text.trim();
1802
+ let value;
1803
+ if (trimmed === "null") {
1804
+ value = null;
1805
+ } else if (trimmed === "true") {
1806
+ value = true;
1807
+ } else if (trimmed === "false") {
1808
+ value = false;
1809
+ } else {
1810
+ const parsed = tryParseJson(trimmed);
1811
+ value = parsed !== null ? parsed : trimmed;
1812
+ }
1813
+ return {
1814
+ kind: "annotation",
1815
+ annotationKind: "defaultValue",
1816
+ value,
1817
+ provenance
1818
+ };
1819
+ }
1820
+ function isMemberTargetDisplayName(text) {
1821
+ return parseMemberTargetDisplayName(text) !== null;
1822
+ }
1823
+ function parseMemberTargetDisplayName(text) {
1824
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1825
+ if (!match?.[1] || !match[2]) return null;
1826
+ return { target: match[1], label: match[2].trim() };
1827
+ }
1475
1828
  function provenanceForComment(range, sourceFile, file, tagName) {
1476
1829
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1477
1830
  return {
@@ -1502,7 +1855,7 @@ function getTagCommentText(tag) {
1502
1855
  }
1503
1856
  return ts2.getTextOfJSDocComment(tag.comment);
1504
1857
  }
1505
- var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, sharedParser;
1858
+ var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, parserCache;
1506
1859
  var init_tsdoc_parser = __esm({
1507
1860
  "src/analyzer/tsdoc-parser.ts"() {
1508
1861
  "use strict";
@@ -1520,18 +1873,19 @@ var init_tsdoc_parser = __esm({
1520
1873
  minItems: "minItems",
1521
1874
  maxItems: "maxItems"
1522
1875
  };
1523
- TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1876
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1877
+ parserCache = /* @__PURE__ */ new Map();
1524
1878
  }
1525
1879
  });
1526
1880
 
1527
1881
  // src/analyzer/jsdoc-constraints.ts
1528
1882
  import * as ts3 from "typescript";
1529
- function extractJSDocConstraintNodes(node, file = "") {
1530
- const result = parseTSDocTags(node, file);
1883
+ function extractJSDocConstraintNodes(node, file = "", options) {
1884
+ const result = parseTSDocTags(node, file, options);
1531
1885
  return [...result.constraints];
1532
1886
  }
1533
- function extractJSDocAnnotationNodes(node, file = "") {
1534
- const result = parseTSDocTags(node, file);
1887
+ function extractJSDocAnnotationNodes(node, file = "", options) {
1888
+ const result = parseTSDocTags(node, file, options);
1535
1889
  return [...result.annotations];
1536
1890
  }
1537
1891
  function extractDefaultValueAnnotation(initializer, file = "") {
@@ -1582,17 +1936,38 @@ function isObjectType(type) {
1582
1936
  function isTypeReference(type) {
1583
1937
  return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1584
1938
  }
1585
- function analyzeClassToIR(classDecl, checker, file = "") {
1939
+ function makeParseOptions(extensionRegistry, fieldType) {
1940
+ if (extensionRegistry === void 0 && fieldType === void 0) {
1941
+ return void 0;
1942
+ }
1943
+ return {
1944
+ ...extensionRegistry !== void 0 && { extensionRegistry },
1945
+ ...fieldType !== void 0 && { fieldType }
1946
+ };
1947
+ }
1948
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1586
1949
  const name = classDecl.name?.text ?? "AnonymousClass";
1587
1950
  const fields = [];
1588
1951
  const fieldLayouts = [];
1589
1952
  const typeRegistry = {};
1953
+ const annotations = extractJSDocAnnotationNodes(
1954
+ classDecl,
1955
+ file,
1956
+ makeParseOptions(extensionRegistry)
1957
+ );
1590
1958
  const visiting = /* @__PURE__ */ new Set();
1591
1959
  const instanceMethods = [];
1592
1960
  const staticMethods = [];
1593
1961
  for (const member of classDecl.members) {
1594
1962
  if (ts4.isPropertyDeclaration(member)) {
1595
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1963
+ const fieldNode = analyzeFieldToIR(
1964
+ member,
1965
+ checker,
1966
+ file,
1967
+ typeRegistry,
1968
+ visiting,
1969
+ extensionRegistry
1970
+ );
1596
1971
  if (fieldNode) {
1597
1972
  fields.push(fieldNode);
1598
1973
  fieldLayouts.push({});
@@ -1609,25 +1984,53 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1609
1984
  }
1610
1985
  }
1611
1986
  }
1612
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1987
+ return {
1988
+ name,
1989
+ fields,
1990
+ fieldLayouts,
1991
+ typeRegistry,
1992
+ ...annotations.length > 0 && { annotations },
1993
+ instanceMethods,
1994
+ staticMethods
1995
+ };
1613
1996
  }
1614
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1997
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
1615
1998
  const name = interfaceDecl.name.text;
1616
1999
  const fields = [];
1617
2000
  const typeRegistry = {};
2001
+ const annotations = extractJSDocAnnotationNodes(
2002
+ interfaceDecl,
2003
+ file,
2004
+ makeParseOptions(extensionRegistry)
2005
+ );
1618
2006
  const visiting = /* @__PURE__ */ new Set();
1619
2007
  for (const member of interfaceDecl.members) {
1620
2008
  if (ts4.isPropertySignature(member)) {
1621
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2009
+ const fieldNode = analyzeInterfacePropertyToIR(
2010
+ member,
2011
+ checker,
2012
+ file,
2013
+ typeRegistry,
2014
+ visiting,
2015
+ extensionRegistry
2016
+ );
1622
2017
  if (fieldNode) {
1623
2018
  fields.push(fieldNode);
1624
2019
  }
1625
2020
  }
1626
2021
  }
1627
2022
  const fieldLayouts = fields.map(() => ({}));
1628
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
2023
+ return {
2024
+ name,
2025
+ fields,
2026
+ fieldLayouts,
2027
+ typeRegistry,
2028
+ ...annotations.length > 0 && { annotations },
2029
+ instanceMethods: [],
2030
+ staticMethods: []
2031
+ };
1629
2032
  }
1630
- function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
2033
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1631
2034
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1632
2035
  const sourceFile = typeAlias.getSourceFile();
1633
2036
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -1640,10 +2043,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1640
2043
  const name = typeAlias.name.text;
1641
2044
  const fields = [];
1642
2045
  const typeRegistry = {};
2046
+ const annotations = extractJSDocAnnotationNodes(
2047
+ typeAlias,
2048
+ file,
2049
+ makeParseOptions(extensionRegistry)
2050
+ );
1643
2051
  const visiting = /* @__PURE__ */ new Set();
1644
2052
  for (const member of typeAlias.type.members) {
1645
2053
  if (ts4.isPropertySignature(member)) {
1646
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2054
+ const fieldNode = analyzeInterfacePropertyToIR(
2055
+ member,
2056
+ checker,
2057
+ file,
2058
+ typeRegistry,
2059
+ visiting,
2060
+ extensionRegistry
2061
+ );
1647
2062
  if (fieldNode) {
1648
2063
  fields.push(fieldNode);
1649
2064
  }
@@ -1656,12 +2071,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1656
2071
  fields,
1657
2072
  fieldLayouts: fields.map(() => ({})),
1658
2073
  typeRegistry,
2074
+ ...annotations.length > 0 && { annotations },
1659
2075
  instanceMethods: [],
1660
2076
  staticMethods: []
1661
2077
  }
1662
2078
  };
1663
2079
  }
1664
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
2080
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1665
2081
  if (!ts4.isIdentifier(prop.name)) {
1666
2082
  return null;
1667
2083
  }
@@ -1669,16 +2085,28 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1669
2085
  const tsType = checker.getTypeAtLocation(prop);
1670
2086
  const optional = prop.questionToken !== void 0;
1671
2087
  const provenance = provenanceForNode(prop, file);
1672
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
2088
+ let type = resolveTypeNode(
2089
+ tsType,
2090
+ checker,
2091
+ file,
2092
+ typeRegistry,
2093
+ visiting,
2094
+ prop,
2095
+ extensionRegistry
2096
+ );
1673
2097
  const constraints = [];
1674
2098
  if (prop.type) {
1675
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2099
+ constraints.push(
2100
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2101
+ );
1676
2102
  }
1677
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2103
+ constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
1678
2104
  let annotations = [];
1679
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2105
+ annotations.push(
2106
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2107
+ );
1680
2108
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1681
- if (defaultAnnotation) {
2109
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1682
2110
  annotations.push(defaultAnnotation);
1683
2111
  }
1684
2112
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
@@ -1692,7 +2120,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1692
2120
  provenance
1693
2121
  };
1694
2122
  }
1695
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
2123
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1696
2124
  if (!ts4.isIdentifier(prop.name)) {
1697
2125
  return null;
1698
2126
  }
@@ -1700,14 +2128,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1700
2128
  const tsType = checker.getTypeAtLocation(prop);
1701
2129
  const optional = prop.questionToken !== void 0;
1702
2130
  const provenance = provenanceForNode(prop, file);
1703
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
2131
+ let type = resolveTypeNode(
2132
+ tsType,
2133
+ checker,
2134
+ file,
2135
+ typeRegistry,
2136
+ visiting,
2137
+ prop,
2138
+ extensionRegistry
2139
+ );
1704
2140
  const constraints = [];
1705
2141
  if (prop.type) {
1706
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2142
+ constraints.push(
2143
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2144
+ );
1707
2145
  }
1708
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2146
+ constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
1709
2147
  let annotations = [];
1710
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2148
+ annotations.push(
2149
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2150
+ );
1711
2151
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1712
2152
  return {
1713
2153
  kind: "field",
@@ -1781,7 +2221,66 @@ function parseEnumMemberDisplayName(value) {
1781
2221
  if (label === "") return null;
1782
2222
  return { value: match[1], label };
1783
2223
  }
1784
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
2224
+ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
2225
+ if (sourceNode === void 0 || extensionRegistry === void 0) {
2226
+ return null;
2227
+ }
2228
+ const typeNode = extractTypeNodeFromSource(sourceNode);
2229
+ if (typeNode === void 0) {
2230
+ return null;
2231
+ }
2232
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
2233
+ }
2234
+ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
2235
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
2236
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
2237
+ }
2238
+ const typeName = getTypeNodeRegistrationName(typeNode);
2239
+ if (typeName === null) {
2240
+ return null;
2241
+ }
2242
+ const registration = extensionRegistry.findTypeByName(typeName);
2243
+ if (registration !== void 0) {
2244
+ return {
2245
+ kind: "custom",
2246
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
2247
+ payload: null
2248
+ };
2249
+ }
2250
+ if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
2251
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
2252
+ if (aliasDecl !== void 0) {
2253
+ return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
2254
+ }
2255
+ }
2256
+ return null;
2257
+ }
2258
+ function extractTypeNodeFromSource(sourceNode) {
2259
+ if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
2260
+ return sourceNode.type;
2261
+ }
2262
+ if (ts4.isTypeNode(sourceNode)) {
2263
+ return sourceNode;
2264
+ }
2265
+ return void 0;
2266
+ }
2267
+ function getTypeNodeRegistrationName(typeNode) {
2268
+ if (ts4.isTypeReferenceNode(typeNode)) {
2269
+ return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
2270
+ }
2271
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
2272
+ return getTypeNodeRegistrationName(typeNode.type);
2273
+ }
2274
+ if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
2275
+ return typeNode.getText();
2276
+ }
2277
+ return null;
2278
+ }
2279
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2280
+ const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2281
+ if (customType) {
2282
+ return customType;
2283
+ }
1785
2284
  if (type.flags & ts4.TypeFlags.String) {
1786
2285
  return { kind: "primitive", primitiveKind: "string" };
1787
2286
  }
@@ -1810,88 +2309,162 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1810
2309
  };
1811
2310
  }
1812
2311
  if (type.isUnion()) {
1813
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
2312
+ return resolveUnionType(
2313
+ type,
2314
+ checker,
2315
+ file,
2316
+ typeRegistry,
2317
+ visiting,
2318
+ sourceNode,
2319
+ extensionRegistry
2320
+ );
1814
2321
  }
1815
2322
  if (checker.isArrayType(type)) {
1816
- return resolveArrayType(type, checker, file, typeRegistry, visiting);
2323
+ return resolveArrayType(
2324
+ type,
2325
+ checker,
2326
+ file,
2327
+ typeRegistry,
2328
+ visiting,
2329
+ sourceNode,
2330
+ extensionRegistry
2331
+ );
1817
2332
  }
1818
2333
  if (isObjectType(type)) {
1819
- return resolveObjectType(type, checker, file, typeRegistry, visiting);
2334
+ return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
1820
2335
  }
1821
2336
  return { kind: "primitive", primitiveKind: "string" };
1822
2337
  }
1823
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
2338
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2339
+ const typeName = getNamedTypeName(type);
2340
+ const namedDecl = getNamedTypeDeclaration(type);
2341
+ if (typeName && typeName in typeRegistry) {
2342
+ return { kind: "reference", name: typeName, typeArguments: [] };
2343
+ }
1824
2344
  const allTypes = type.types;
2345
+ const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
2346
+ const nonNullSourceNodes = unionMemberTypeNodes.filter(
2347
+ (memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
2348
+ );
1825
2349
  const nonNullTypes = allTypes.filter(
1826
- (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
2350
+ (memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1827
2351
  );
2352
+ const nonNullMembers = nonNullTypes.map((memberType, index) => ({
2353
+ memberType,
2354
+ sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
2355
+ }));
1828
2356
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2357
+ const memberDisplayNames = /* @__PURE__ */ new Map();
2358
+ if (namedDecl) {
2359
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
2360
+ memberDisplayNames.set(value, label);
2361
+ }
2362
+ }
2363
+ if (sourceNode) {
2364
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
2365
+ memberDisplayNames.set(value, label);
2366
+ }
2367
+ }
2368
+ const registerNamed = (result) => {
2369
+ if (!typeName) {
2370
+ return result;
2371
+ }
2372
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2373
+ typeRegistry[typeName] = {
2374
+ name: typeName,
2375
+ type: result,
2376
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2377
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
2378
+ };
2379
+ return { kind: "reference", name: typeName, typeArguments: [] };
2380
+ };
2381
+ const applyMemberLabels = (members2) => members2.map((value) => {
2382
+ const displayName = memberDisplayNames.get(String(value));
2383
+ return displayName !== void 0 ? { value, displayName } : { value };
2384
+ });
1829
2385
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1830
2386
  if (isBooleanUnion2) {
1831
2387
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1832
- if (hasNull) {
1833
- return {
1834
- kind: "union",
1835
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1836
- };
1837
- }
1838
- return boolNode;
2388
+ const result = hasNull ? {
2389
+ kind: "union",
2390
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
2391
+ } : boolNode;
2392
+ return registerNamed(result);
1839
2393
  }
1840
2394
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1841
2395
  if (allStringLiterals && nonNullTypes.length > 0) {
1842
2396
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1843
2397
  const enumNode = {
1844
2398
  kind: "enum",
1845
- members: stringTypes.map((t) => ({ value: t.value }))
2399
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1846
2400
  };
1847
- if (hasNull) {
1848
- return {
1849
- kind: "union",
1850
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1851
- };
1852
- }
1853
- return enumNode;
2401
+ const result = hasNull ? {
2402
+ kind: "union",
2403
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2404
+ } : enumNode;
2405
+ return registerNamed(result);
1854
2406
  }
1855
2407
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1856
2408
  if (allNumberLiterals && nonNullTypes.length > 0) {
1857
2409
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1858
2410
  const enumNode = {
1859
2411
  kind: "enum",
1860
- members: numberTypes.map((t) => ({ value: t.value }))
2412
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1861
2413
  };
1862
- if (hasNull) {
1863
- return {
1864
- kind: "union",
1865
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1866
- };
1867
- }
1868
- return enumNode;
1869
- }
1870
- if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1871
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1872
- if (hasNull) {
1873
- return {
1874
- kind: "union",
1875
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1876
- };
1877
- }
1878
- return inner;
1879
- }
1880
- const members = nonNullTypes.map(
1881
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2414
+ const result = hasNull ? {
2415
+ kind: "union",
2416
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2417
+ } : enumNode;
2418
+ return registerNamed(result);
2419
+ }
2420
+ if (nonNullMembers.length === 1 && nonNullMembers[0]) {
2421
+ const inner = resolveTypeNode(
2422
+ nonNullMembers[0].memberType,
2423
+ checker,
2424
+ file,
2425
+ typeRegistry,
2426
+ visiting,
2427
+ nonNullMembers[0].sourceNode ?? sourceNode,
2428
+ extensionRegistry
2429
+ );
2430
+ const result = hasNull ? {
2431
+ kind: "union",
2432
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2433
+ } : inner;
2434
+ return registerNamed(result);
2435
+ }
2436
+ const members = nonNullMembers.map(
2437
+ ({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
2438
+ memberType,
2439
+ checker,
2440
+ file,
2441
+ typeRegistry,
2442
+ visiting,
2443
+ memberSourceNode ?? sourceNode,
2444
+ extensionRegistry
2445
+ )
1882
2446
  );
1883
2447
  if (hasNull) {
1884
2448
  members.push({ kind: "primitive", primitiveKind: "null" });
1885
2449
  }
1886
- return { kind: "union", members };
2450
+ return registerNamed({ kind: "union", members });
1887
2451
  }
1888
- function resolveArrayType(type, checker, file, typeRegistry, visiting) {
2452
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1889
2453
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
1890
2454
  const elementType = typeArgs?.[0];
1891
- const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
2455
+ const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
2456
+ const items = elementType ? resolveTypeNode(
2457
+ elementType,
2458
+ checker,
2459
+ file,
2460
+ typeRegistry,
2461
+ visiting,
2462
+ elementSourceNode,
2463
+ extensionRegistry
2464
+ ) : { kind: "primitive", primitiveKind: "string" };
1892
2465
  return { kind: "array", items };
1893
2466
  }
1894
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2467
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1895
2468
  if (type.getProperties().length > 0) {
1896
2469
  return null;
1897
2470
  }
@@ -1899,39 +2472,123 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1899
2472
  if (!indexInfo) {
1900
2473
  return null;
1901
2474
  }
1902
- if (visiting.has(type)) {
1903
- return null;
1904
- }
1905
- visiting.add(type);
1906
- try {
1907
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1908
- return { kind: "record", valueType };
1909
- } finally {
1910
- visiting.delete(type);
1911
- }
2475
+ const valueType = resolveTypeNode(
2476
+ indexInfo.type,
2477
+ checker,
2478
+ file,
2479
+ typeRegistry,
2480
+ visiting,
2481
+ void 0,
2482
+ extensionRegistry
2483
+ );
2484
+ return { kind: "record", valueType };
1912
2485
  }
1913
- function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1914
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1915
- if (recordNode) {
1916
- return recordNode;
2486
+ function typeNodeContainsReference(type, targetName) {
2487
+ switch (type.kind) {
2488
+ case "reference":
2489
+ return type.name === targetName;
2490
+ case "array":
2491
+ return typeNodeContainsReference(type.items, targetName);
2492
+ case "record":
2493
+ return typeNodeContainsReference(type.valueType, targetName);
2494
+ case "union":
2495
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2496
+ case "object":
2497
+ return type.properties.some(
2498
+ (property) => typeNodeContainsReference(property.type, targetName)
2499
+ );
2500
+ case "primitive":
2501
+ case "enum":
2502
+ case "dynamic":
2503
+ case "custom":
2504
+ return false;
2505
+ default: {
2506
+ const _exhaustive = type;
2507
+ return _exhaustive;
2508
+ }
1917
2509
  }
2510
+ }
2511
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2512
+ const typeName = getNamedTypeName(type);
2513
+ const namedTypeName = typeName ?? void 0;
2514
+ const namedDecl = getNamedTypeDeclaration(type);
2515
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2516
+ const clearNamedTypeRegistration = () => {
2517
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2518
+ return;
2519
+ }
2520
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2521
+ };
1918
2522
  if (visiting.has(type)) {
2523
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2524
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2525
+ }
1919
2526
  return { kind: "object", properties: [], additionalProperties: false };
1920
2527
  }
2528
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2529
+ typeRegistry[namedTypeName] = {
2530
+ name: namedTypeName,
2531
+ type: RESOLVING_TYPE_PLACEHOLDER,
2532
+ provenance: provenanceForDeclaration(namedDecl, file)
2533
+ };
2534
+ }
1921
2535
  visiting.add(type);
1922
- const typeName = getNamedTypeName(type);
1923
- if (typeName && typeName in typeRegistry) {
2536
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2537
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2538
+ visiting.delete(type);
2539
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2540
+ }
2541
+ }
2542
+ const recordNode = tryResolveRecordType(
2543
+ type,
2544
+ checker,
2545
+ file,
2546
+ typeRegistry,
2547
+ visiting,
2548
+ extensionRegistry
2549
+ );
2550
+ if (recordNode) {
1924
2551
  visiting.delete(type);
1925
- return { kind: "reference", name: typeName, typeArguments: [] };
2552
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2553
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2554
+ if (!isRecursiveRecord) {
2555
+ clearNamedTypeRegistration();
2556
+ return recordNode;
2557
+ }
2558
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2559
+ typeRegistry[namedTypeName] = {
2560
+ name: namedTypeName,
2561
+ type: recordNode,
2562
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2563
+ provenance: provenanceForDeclaration(namedDecl, file)
2564
+ };
2565
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2566
+ }
2567
+ return recordNode;
1926
2568
  }
1927
2569
  const properties = [];
1928
- const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
2570
+ const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
2571
+ type,
2572
+ checker,
2573
+ file,
2574
+ typeRegistry,
2575
+ visiting,
2576
+ extensionRegistry
2577
+ );
1929
2578
  for (const prop of type.getProperties()) {
1930
2579
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1931
2580
  if (!declaration) continue;
1932
2581
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1933
2582
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1934
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2583
+ const propTypeNode = resolveTypeNode(
2584
+ propType,
2585
+ checker,
2586
+ file,
2587
+ typeRegistry,
2588
+ visiting,
2589
+ declaration,
2590
+ extensionRegistry
2591
+ );
1935
2592
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1936
2593
  properties.push({
1937
2594
  name: prop.name,
@@ -1948,17 +2605,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1948
2605
  properties,
1949
2606
  additionalProperties: true
1950
2607
  };
1951
- if (typeName) {
1952
- typeRegistry[typeName] = {
1953
- name: typeName,
2608
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2609
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2610
+ typeRegistry[namedTypeName] = {
2611
+ name: namedTypeName,
1954
2612
  type: objectNode,
1955
- provenance: provenanceForFile(file)
2613
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2614
+ provenance: provenanceForDeclaration(namedDecl, file)
1956
2615
  };
1957
- return { kind: "reference", name: typeName, typeArguments: [] };
2616
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1958
2617
  }
1959
2618
  return objectNode;
1960
2619
  }
1961
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
2620
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1962
2621
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
1963
2622
  (s) => s?.declarations != null && s.declarations.length > 0
1964
2623
  );
@@ -1970,7 +2629,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1970
2629
  const map = /* @__PURE__ */ new Map();
1971
2630
  for (const member of classDecl.members) {
1972
2631
  if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
1973
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
2632
+ const fieldNode = analyzeFieldToIR(
2633
+ member,
2634
+ checker,
2635
+ file,
2636
+ typeRegistry,
2637
+ visiting,
2638
+ extensionRegistry
2639
+ );
1974
2640
  if (fieldNode) {
1975
2641
  map.set(fieldNode.name, {
1976
2642
  constraints: [...fieldNode.constraints],
@@ -1984,7 +2650,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1984
2650
  }
1985
2651
  const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
1986
2652
  if (interfaceDecl) {
1987
- return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
2653
+ return buildFieldNodeInfoMap(
2654
+ interfaceDecl.members,
2655
+ checker,
2656
+ file,
2657
+ typeRegistry,
2658
+ visiting,
2659
+ extensionRegistry
2660
+ );
1988
2661
  }
1989
2662
  const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
1990
2663
  if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
@@ -1993,17 +2666,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1993
2666
  checker,
1994
2667
  file,
1995
2668
  typeRegistry,
1996
- visiting
2669
+ visiting,
2670
+ extensionRegistry
1997
2671
  );
1998
2672
  }
1999
2673
  }
2000
2674
  return null;
2001
2675
  }
2002
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2676
+ function extractArrayElementTypeNode(sourceNode, checker) {
2677
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2678
+ if (typeNode === void 0) {
2679
+ return void 0;
2680
+ }
2681
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2682
+ if (ts4.isArrayTypeNode(resolvedTypeNode)) {
2683
+ return resolvedTypeNode.elementType;
2684
+ }
2685
+ if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
2686
+ return resolvedTypeNode.typeArguments[0];
2687
+ }
2688
+ return void 0;
2689
+ }
2690
+ function extractUnionMemberTypeNodes(sourceNode, checker) {
2691
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2692
+ if (!typeNode) {
2693
+ return [];
2694
+ }
2695
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2696
+ return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
2697
+ }
2698
+ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
2699
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
2700
+ return resolveAliasedTypeNode(typeNode.type, checker, visited);
2701
+ }
2702
+ if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
2703
+ return typeNode;
2704
+ }
2705
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2706
+ const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
2707
+ if (aliasDecl === void 0 || visited.has(aliasDecl)) {
2708
+ return typeNode;
2709
+ }
2710
+ visited.add(aliasDecl);
2711
+ return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
2712
+ }
2713
+ function isNullishTypeNode(typeNode) {
2714
+ if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
2715
+ return true;
2716
+ }
2717
+ return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
2718
+ }
2719
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
2003
2720
  const map = /* @__PURE__ */ new Map();
2004
2721
  for (const member of members) {
2005
2722
  if (ts4.isPropertySignature(member)) {
2006
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2723
+ const fieldNode = analyzeInterfacePropertyToIR(
2724
+ member,
2725
+ checker,
2726
+ file,
2727
+ typeRegistry,
2728
+ visiting,
2729
+ extensionRegistry
2730
+ );
2007
2731
  if (fieldNode) {
2008
2732
  map.set(fieldNode.name, {
2009
2733
  constraints: [...fieldNode.constraints],
@@ -2015,7 +2739,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2015
2739
  }
2016
2740
  return map;
2017
2741
  }
2018
- function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2742
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
2019
2743
  if (!ts4.isTypeReferenceNode(typeNode)) return [];
2020
2744
  if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
2021
2745
  const aliasName = typeNode.typeName.getText();
@@ -2028,8 +2752,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2028
2752
  const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
2029
2753
  if (!aliasDecl) return [];
2030
2754
  if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
2031
- const constraints = extractJSDocConstraintNodes(aliasDecl, file);
2032
- constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
2755
+ const aliasFieldType = resolveTypeNode(
2756
+ checker.getTypeAtLocation(aliasDecl.type),
2757
+ checker,
2758
+ file,
2759
+ {},
2760
+ /* @__PURE__ */ new Set(),
2761
+ aliasDecl.type,
2762
+ extensionRegistry
2763
+ );
2764
+ const constraints = extractJSDocConstraintNodes(
2765
+ aliasDecl,
2766
+ file,
2767
+ makeParseOptions(extensionRegistry, aliasFieldType)
2768
+ );
2769
+ constraints.push(
2770
+ ...extractTypeAliasConstraintNodes(
2771
+ aliasDecl.type,
2772
+ checker,
2773
+ file,
2774
+ extensionRegistry,
2775
+ depth + 1
2776
+ )
2777
+ );
2033
2778
  return constraints;
2034
2779
  }
2035
2780
  function provenanceForNode(node, file) {
@@ -2045,6 +2790,12 @@ function provenanceForNode(node, file) {
2045
2790
  function provenanceForFile(file) {
2046
2791
  return { surface: "tsdoc", file, line: 0, column: 0 };
2047
2792
  }
2793
+ function provenanceForDeclaration(node, file) {
2794
+ if (!node) {
2795
+ return provenanceForFile(file);
2796
+ }
2797
+ return provenanceForNode(node, file);
2798
+ }
2048
2799
  function getNamedTypeName(type) {
2049
2800
  const symbol = type.getSymbol();
2050
2801
  if (symbol?.declarations) {
@@ -2063,6 +2814,20 @@ function getNamedTypeName(type) {
2063
2814
  }
2064
2815
  return null;
2065
2816
  }
2817
+ function getNamedTypeDeclaration(type) {
2818
+ const symbol = type.getSymbol();
2819
+ if (symbol?.declarations) {
2820
+ const decl = symbol.declarations[0];
2821
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2822
+ return decl;
2823
+ }
2824
+ }
2825
+ const aliasSymbol = type.aliasSymbol;
2826
+ if (aliasSymbol?.declarations) {
2827
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2828
+ }
2829
+ return void 0;
2830
+ }
2066
2831
  function analyzeMethod(method, checker) {
2067
2832
  if (!ts4.isIdentifier(method.name)) {
2068
2833
  return null;
@@ -2103,20 +2868,26 @@ function detectFormSpecReference(typeNode) {
2103
2868
  }
2104
2869
  return null;
2105
2870
  }
2106
- var MAX_ALIAS_CHAIN_DEPTH;
2871
+ var RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
2107
2872
  var init_class_analyzer = __esm({
2108
2873
  "src/analyzer/class-analyzer.ts"() {
2109
2874
  "use strict";
2110
2875
  init_jsdoc_constraints();
2876
+ init_tsdoc_parser();
2877
+ RESOLVING_TYPE_PLACEHOLDER = {
2878
+ kind: "object",
2879
+ properties: [],
2880
+ additionalProperties: true
2881
+ };
2111
2882
  MAX_ALIAS_CHAIN_DEPTH = 8;
2112
2883
  }
2113
2884
  });
2114
2885
 
2115
2886
  // src/generators/class-schema.ts
2116
- function generateClassSchemas(analysis, source) {
2887
+ function generateClassSchemas(analysis, source, options) {
2117
2888
  const ir = canonicalizeTSDoc(analysis, source);
2118
2889
  return {
2119
- jsonSchema: generateJsonSchemaFromIR(ir),
2890
+ jsonSchema: generateJsonSchemaFromIR(ir, options),
2120
2891
  uiSchema: generateUiSchemaFromIR(ir)
2121
2892
  };
2122
2893
  }
@@ -2126,27 +2897,54 @@ function generateSchemasFromClass(options) {
2126
2897
  if (!classDecl) {
2127
2898
  throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
2128
2899
  }
2129
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2130
- return generateClassSchemas(analysis, { file: options.filePath });
2900
+ const analysis = analyzeClassToIR(
2901
+ classDecl,
2902
+ ctx.checker,
2903
+ options.filePath,
2904
+ options.extensionRegistry
2905
+ );
2906
+ return generateClassSchemas(
2907
+ analysis,
2908
+ { file: options.filePath },
2909
+ {
2910
+ extensionRegistry: options.extensionRegistry,
2911
+ vendorPrefix: options.vendorPrefix
2912
+ }
2913
+ );
2131
2914
  }
2132
2915
  function generateSchemas(options) {
2133
2916
  const ctx = createProgramContext(options.filePath);
2134
2917
  const source = { file: options.filePath };
2135
2918
  const classDecl = findClassByName(ctx.sourceFile, options.typeName);
2136
2919
  if (classDecl) {
2137
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2138
- return generateClassSchemas(analysis, source);
2920
+ const analysis = analyzeClassToIR(
2921
+ classDecl,
2922
+ ctx.checker,
2923
+ options.filePath,
2924
+ options.extensionRegistry
2925
+ );
2926
+ return generateClassSchemas(analysis, source, options);
2139
2927
  }
2140
2928
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
2141
2929
  if (interfaceDecl) {
2142
- const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
2143
- return generateClassSchemas(analysis, source);
2930
+ const analysis = analyzeInterfaceToIR(
2931
+ interfaceDecl,
2932
+ ctx.checker,
2933
+ options.filePath,
2934
+ options.extensionRegistry
2935
+ );
2936
+ return generateClassSchemas(analysis, source, options);
2144
2937
  }
2145
2938
  const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
2146
2939
  if (typeAlias) {
2147
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
2940
+ const result = analyzeTypeAliasToIR(
2941
+ typeAlias,
2942
+ ctx.checker,
2943
+ options.filePath,
2944
+ options.extensionRegistry
2945
+ );
2148
2946
  if (result.ok) {
2149
- return generateClassSchemas(result.analysis, source);
2947
+ return generateClassSchemas(result.analysis, source, options);
2150
2948
  }
2151
2949
  throw new Error(result.error);
2152
2950
  }
@@ -2165,10 +2963,220 @@ var init_class_schema = __esm({
2165
2963
  }
2166
2964
  });
2167
2965
 
2966
+ // src/generators/mixed-authoring.ts
2967
+ function buildMixedAuthoringSchemas(options) {
2968
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2969
+ const analysis = analyzeNamedType(filePath, typeName, schemaOptions.extensionRegistry);
2970
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2971
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2972
+ return {
2973
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2974
+ uiSchema: generateUiSchemaFromIR(ir)
2975
+ };
2976
+ }
2977
+ function analyzeNamedType(filePath, typeName, extensionRegistry) {
2978
+ const ctx = createProgramContext(filePath);
2979
+ const source = { file: filePath };
2980
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2981
+ if (classDecl !== null) {
2982
+ return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
2983
+ }
2984
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2985
+ if (interfaceDecl !== null) {
2986
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
2987
+ }
2988
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2989
+ if (typeAlias !== null) {
2990
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
2991
+ if (result.ok) {
2992
+ return result.analysis;
2993
+ }
2994
+ throw new Error(result.error);
2995
+ }
2996
+ throw new Error(
2997
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2998
+ );
2999
+ }
3000
+ function composeAnalysisWithOverlays(analysis, overlays) {
3001
+ const overlayIR = canonicalizeChainDSL(overlays);
3002
+ const overlayFields = collectOverlayFields(overlayIR.elements);
3003
+ if (overlayFields.length === 0) {
3004
+ return analysis;
3005
+ }
3006
+ const overlayByName = /* @__PURE__ */ new Map();
3007
+ for (const field of overlayFields) {
3008
+ if (overlayByName.has(field.name)) {
3009
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
3010
+ }
3011
+ overlayByName.set(field.name, field);
3012
+ }
3013
+ const mergedFields = [];
3014
+ for (const baseField of analysis.fields) {
3015
+ const overlayField = overlayByName.get(baseField.name);
3016
+ if (overlayField === void 0) {
3017
+ mergedFields.push(baseField);
3018
+ continue;
3019
+ }
3020
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
3021
+ overlayByName.delete(baseField.name);
3022
+ }
3023
+ if (overlayByName.size > 0) {
3024
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
3025
+ throw new Error(
3026
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
3027
+ );
3028
+ }
3029
+ return {
3030
+ ...analysis,
3031
+ fields: mergedFields
3032
+ };
3033
+ }
3034
+ function collectOverlayFields(elements) {
3035
+ const fields = [];
3036
+ for (const element of elements) {
3037
+ switch (element.kind) {
3038
+ case "field":
3039
+ fields.push(element);
3040
+ break;
3041
+ case "group":
3042
+ fields.push(...collectOverlayFields(element.elements));
3043
+ break;
3044
+ case "conditional":
3045
+ fields.push(...collectOverlayFields(element.elements));
3046
+ break;
3047
+ default: {
3048
+ const _exhaustive = element;
3049
+ void _exhaustive;
3050
+ }
3051
+ }
3052
+ }
3053
+ return fields;
3054
+ }
3055
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
3056
+ assertSupportedOverlayField(baseField, overlayField);
3057
+ return {
3058
+ ...baseField,
3059
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
3060
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
3061
+ };
3062
+ }
3063
+ function assertSupportedOverlayField(baseField, overlayField) {
3064
+ if (overlayField.constraints.length > 0) {
3065
+ throw new Error(
3066
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
3067
+ );
3068
+ }
3069
+ if (overlayField.required && !baseField.required) {
3070
+ throw new Error(
3071
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
3072
+ );
3073
+ }
3074
+ }
3075
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
3076
+ const { type: baseType } = baseField;
3077
+ const { type: overlayType } = overlayField;
3078
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
3079
+ throw new Error(
3080
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
3081
+ );
3082
+ }
3083
+ if (overlayType.kind === "dynamic") {
3084
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
3085
+ throw new Error(
3086
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
3087
+ );
3088
+ }
3089
+ return overlayType;
3090
+ }
3091
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
3092
+ throw new Error(
3093
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
3094
+ );
3095
+ }
3096
+ return baseType;
3097
+ }
3098
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
3099
+ const overlayType = overlayField.type;
3100
+ if (overlayType.kind !== "dynamic") {
3101
+ return false;
3102
+ }
3103
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
3104
+ if (resolvedBaseType === null) {
3105
+ return false;
3106
+ }
3107
+ if (overlayType.dynamicKind === "enum") {
3108
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
3109
+ }
3110
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
3111
+ }
3112
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
3113
+ if (type.kind !== "reference") {
3114
+ return type;
3115
+ }
3116
+ if (seen.has(type.name)) {
3117
+ return null;
3118
+ }
3119
+ const definition = typeRegistry[type.name];
3120
+ if (definition === void 0) {
3121
+ return null;
3122
+ }
3123
+ seen.add(type.name);
3124
+ return resolveReferenceType(definition.type, typeRegistry, seen);
3125
+ }
3126
+ function isSameStaticTypeShape(baseType, overlayType) {
3127
+ if (baseType.kind !== overlayType.kind) {
3128
+ return false;
3129
+ }
3130
+ switch (baseType.kind) {
3131
+ case "primitive":
3132
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
3133
+ case "enum":
3134
+ return overlayType.kind === "enum";
3135
+ case "dynamic":
3136
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
3137
+ case "record":
3138
+ return overlayType.kind === "record";
3139
+ case "reference":
3140
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
3141
+ case "union":
3142
+ return overlayType.kind === "union";
3143
+ case "custom":
3144
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
3145
+ case "object":
3146
+ case "array":
3147
+ return true;
3148
+ default: {
3149
+ const _exhaustive = baseType;
3150
+ return _exhaustive;
3151
+ }
3152
+ }
3153
+ }
3154
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
3155
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
3156
+ const overlayOnly = overlayAnnotations.filter(
3157
+ (annotation) => !baseKeys.has(annotationKey(annotation))
3158
+ );
3159
+ return [...baseAnnotations, ...overlayOnly];
3160
+ }
3161
+ function annotationKey(annotation) {
3162
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
3163
+ }
3164
+ var init_mixed_authoring = __esm({
3165
+ "src/generators/mixed-authoring.ts"() {
3166
+ "use strict";
3167
+ init_ir_generator();
3168
+ init_ir_generator2();
3169
+ init_canonicalize();
3170
+ init_program();
3171
+ init_class_analyzer();
3172
+ }
3173
+ });
3174
+
2168
3175
  // src/index.ts
2169
3176
  var index_exports = {};
2170
3177
  __export(index_exports, {
2171
3178
  buildFormSchemas: () => buildFormSchemas,
3179
+ buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
2172
3180
  categorizationSchema: () => categorizationSchema,
2173
3181
  categorySchema: () => categorySchema,
2174
3182
  controlSchema: () => controlSchema,
@@ -2233,6 +3241,7 @@ var init_index = __esm({
2233
3241
  init_ir_generator();
2234
3242
  init_generator2();
2235
3243
  init_class_schema();
3244
+ init_mixed_authoring();
2236
3245
  }
2237
3246
  });
2238
3247