@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.cjs CHANGED
@@ -362,6 +362,7 @@ function canonicalizeTSDoc(analysis, source) {
362
362
  irVersion: import_core2.IR_VERSION,
363
363
  elements,
364
364
  typeRegistry: analysis.typeRegistry,
365
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
365
366
  provenance
366
367
  };
367
368
  }
@@ -454,6 +455,9 @@ function generateJsonSchemaFromIR(ir, options) {
454
455
  const ctx = makeContext(options);
455
456
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
456
457
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
458
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
459
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
460
+ }
457
461
  }
458
462
  const properties = {};
459
463
  const required = [];
@@ -465,6 +469,9 @@ function generateJsonSchemaFromIR(ir, options) {
465
469
  properties,
466
470
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
467
471
  };
472
+ if (ir.annotations && ir.annotations.length > 0) {
473
+ applyAnnotations(result, ir.annotations, ctx);
474
+ }
468
475
  if (Object.keys(ctx.defs).length > 0) {
469
476
  result.$defs = ctx.defs;
470
477
  }
@@ -494,22 +501,51 @@ function collectFields(elements, properties, required, ctx) {
494
501
  }
495
502
  function generateFieldSchema(field, ctx) {
496
503
  const schema = generateTypeNode(field.type, ctx);
504
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
497
505
  const directConstraints = [];
506
+ const itemConstraints = [];
498
507
  const pathConstraints = [];
499
508
  for (const c of field.constraints) {
500
509
  if (c.path) {
501
510
  pathConstraints.push(c);
511
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
512
+ itemConstraints.push(c);
502
513
  } else {
503
514
  directConstraints.push(c);
504
515
  }
505
516
  }
506
517
  applyConstraints(schema, directConstraints, ctx);
507
- applyAnnotations(schema, field.annotations, ctx);
518
+ if (itemStringSchema !== void 0) {
519
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
520
+ }
521
+ const rootAnnotations = [];
522
+ const itemAnnotations = [];
523
+ for (const annotation of field.annotations) {
524
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
525
+ itemAnnotations.push(annotation);
526
+ } else {
527
+ rootAnnotations.push(annotation);
528
+ }
529
+ }
530
+ applyAnnotations(schema, rootAnnotations, ctx);
531
+ if (itemStringSchema !== void 0) {
532
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
533
+ }
508
534
  if (pathConstraints.length === 0) {
509
535
  return schema;
510
536
  }
511
537
  return applyPathTargetedConstraints(schema, pathConstraints, ctx);
512
538
  }
539
+ function isStringItemConstraint(constraint) {
540
+ switch (constraint.constraintKind) {
541
+ case "minLength":
542
+ case "maxLength":
543
+ case "pattern":
544
+ return true;
545
+ default:
546
+ return false;
547
+ }
548
+ }
513
549
  function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
514
550
  if (schema.type === "array" && schema.items) {
515
551
  schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
@@ -727,6 +763,9 @@ function applyConstraints(schema, constraints, ctx) {
727
763
  case "uniqueItems":
728
764
  schema.uniqueItems = constraint.value;
729
765
  break;
766
+ case "const":
767
+ schema.const = constraint.value;
768
+ break;
730
769
  case "allowedMembers":
731
770
  break;
732
771
  case "custom":
@@ -751,8 +790,14 @@ function applyAnnotations(schema, annotations, ctx) {
751
790
  case "defaultValue":
752
791
  schema.default = annotation.value;
753
792
  break;
793
+ case "format":
794
+ schema.format = annotation.value;
795
+ break;
754
796
  case "deprecated":
755
797
  schema.deprecated = true;
798
+ if (annotation.message !== void 0 && annotation.message !== "") {
799
+ schema["x-formspec-deprecation-description"] = annotation.message;
800
+ }
756
801
  break;
757
802
  case "placeholder":
758
803
  break;
@@ -784,7 +829,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
784
829
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
785
830
  );
786
831
  }
787
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
832
+ assignVendorPrefixedExtensionKeywords(
833
+ schema,
834
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
835
+ ctx.vendorPrefix,
836
+ `custom constraint "${constraint.constraintId}"`
837
+ );
788
838
  }
789
839
  function applyCustomAnnotation(schema, annotation, ctx) {
790
840
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -796,7 +846,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
796
846
  if (registration.toJsonSchema === void 0) {
797
847
  return;
798
848
  }
799
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
849
+ assignVendorPrefixedExtensionKeywords(
850
+ schema,
851
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
852
+ ctx.vendorPrefix,
853
+ `custom annotation "${annotation.annotationId}"`
854
+ );
855
+ }
856
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
857
+ for (const [key, value] of Object.entries(extensionSchema)) {
858
+ if (!key.startsWith(`${vendorPrefix}-`)) {
859
+ throw new Error(
860
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
861
+ );
862
+ }
863
+ schema[key] = value;
864
+ }
800
865
  }
801
866
  var init_ir_generator = __esm({
802
867
  "src/json-schema/ir-generator.ts"() {
@@ -957,25 +1022,31 @@ function createShowRule(fieldName, value) {
957
1022
  }
958
1023
  };
959
1024
  }
1025
+ function flattenConditionSchema(scope, schema) {
1026
+ if (schema.allOf === void 0) {
1027
+ if (scope === "#") {
1028
+ return [schema];
1029
+ }
1030
+ const fieldName = scope.replace("#/properties/", "");
1031
+ return [
1032
+ {
1033
+ properties: {
1034
+ [fieldName]: schema
1035
+ }
1036
+ }
1037
+ ];
1038
+ }
1039
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
1040
+ }
960
1041
  function combineRules(parentRule, childRule) {
961
- const parentCondition = parentRule.condition;
962
- const childCondition = childRule.condition;
963
1042
  return {
964
1043
  effect: "SHOW",
965
1044
  condition: {
966
1045
  scope: "#",
967
1046
  schema: {
968
1047
  allOf: [
969
- {
970
- properties: {
971
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
972
- }
973
- },
974
- {
975
- properties: {
976
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
977
- }
978
- }
1048
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
1049
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
979
1050
  ]
980
1051
  }
981
1052
  }
@@ -983,10 +1054,14 @@ function combineRules(parentRule, childRule) {
983
1054
  }
984
1055
  function fieldNodeToControl(field, parentRule) {
985
1056
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1057
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
986
1058
  const control = {
987
1059
  type: "Control",
988
1060
  scope: fieldToScope(field.name),
989
1061
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1062
+ ...placeholderAnnotation !== void 0 && {
1063
+ options: { placeholder: placeholderAnnotation.value }
1064
+ },
990
1065
  ...parentRule !== void 0 && { rule: parentRule }
991
1066
  };
992
1067
  return control;
@@ -1072,7 +1147,10 @@ var init_types = __esm({
1072
1147
  // src/extensions/registry.ts
1073
1148
  function createExtensionRegistry(extensions) {
1074
1149
  const typeMap = /* @__PURE__ */ new Map();
1150
+ const typeNameMap = /* @__PURE__ */ new Map();
1075
1151
  const constraintMap = /* @__PURE__ */ new Map();
1152
+ const constraintTagMap = /* @__PURE__ */ new Map();
1153
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
1076
1154
  const annotationMap = /* @__PURE__ */ new Map();
1077
1155
  for (const ext of extensions) {
1078
1156
  if (ext.types !== void 0) {
@@ -1082,6 +1160,27 @@ function createExtensionRegistry(extensions) {
1082
1160
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1083
1161
  }
1084
1162
  typeMap.set(qualifiedId, type);
1163
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
1164
+ if (typeNameMap.has(sourceTypeName)) {
1165
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
1166
+ }
1167
+ typeNameMap.set(sourceTypeName, {
1168
+ extensionId: ext.extensionId,
1169
+ registration: type
1170
+ });
1171
+ }
1172
+ if (type.builtinConstraintBroadenings !== void 0) {
1173
+ for (const broadening of type.builtinConstraintBroadenings) {
1174
+ const key = `${qualifiedId}:${broadening.tagName}`;
1175
+ if (builtinBroadeningMap.has(key)) {
1176
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
1177
+ }
1178
+ builtinBroadeningMap.set(key, {
1179
+ extensionId: ext.extensionId,
1180
+ registration: broadening
1181
+ });
1182
+ }
1183
+ }
1085
1184
  }
1086
1185
  }
1087
1186
  if (ext.constraints !== void 0) {
@@ -1093,6 +1192,17 @@ function createExtensionRegistry(extensions) {
1093
1192
  constraintMap.set(qualifiedId, constraint);
1094
1193
  }
1095
1194
  }
1195
+ if (ext.constraintTags !== void 0) {
1196
+ for (const tag of ext.constraintTags) {
1197
+ if (constraintTagMap.has(tag.tagName)) {
1198
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
1199
+ }
1200
+ constraintTagMap.set(tag.tagName, {
1201
+ extensionId: ext.extensionId,
1202
+ registration: tag
1203
+ });
1204
+ }
1205
+ }
1096
1206
  if (ext.annotations !== void 0) {
1097
1207
  for (const annotation of ext.annotations) {
1098
1208
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -1106,7 +1216,10 @@ function createExtensionRegistry(extensions) {
1106
1216
  return {
1107
1217
  extensions,
1108
1218
  findType: (typeId) => typeMap.get(typeId),
1219
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
1109
1220
  findConstraint: (constraintId) => constraintMap.get(constraintId),
1221
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
1222
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
1110
1223
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
1111
1224
  };
1112
1225
  }
@@ -1285,7 +1398,7 @@ var init_json_utils = __esm({
1285
1398
  });
1286
1399
 
1287
1400
  // src/analyzer/tsdoc-parser.ts
1288
- function createFormSpecTSDocConfig() {
1401
+ function createFormSpecTSDocConfig(extensionTagNames = []) {
1289
1402
  const config = new import_tsdoc.TSDocConfiguration();
1290
1403
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
1291
1404
  config.addTagDefinition(
@@ -1296,7 +1409,16 @@ function createFormSpecTSDocConfig() {
1296
1409
  })
1297
1410
  );
1298
1411
  }
1299
- for (const tagName of ["displayName", "description"]) {
1412
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1413
+ config.addTagDefinition(
1414
+ new import_tsdoc.TSDocTagDefinition({
1415
+ tagName: "@" + tagName,
1416
+ syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
1417
+ allowMultiple: true
1418
+ })
1419
+ );
1420
+ }
1421
+ for (const tagName of extensionTagNames) {
1300
1422
  config.addTagDefinition(
1301
1423
  new import_tsdoc.TSDocTagDefinition({
1302
1424
  tagName: "@" + tagName,
@@ -1307,13 +1429,30 @@ function createFormSpecTSDocConfig() {
1307
1429
  }
1308
1430
  return config;
1309
1431
  }
1310
- function getParser() {
1311
- sharedParser ??= new import_tsdoc.TSDocParser(createFormSpecTSDocConfig());
1312
- return sharedParser;
1313
- }
1314
- function parseTSDocTags(node, file = "") {
1432
+ function getParser(options) {
1433
+ const extensionTagNames = [
1434
+ ...options?.extensionRegistry?.extensions.flatMap(
1435
+ (extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
1436
+ ) ?? []
1437
+ ].sort();
1438
+ const cacheKey = extensionTagNames.join("|");
1439
+ const existing = parserCache.get(cacheKey);
1440
+ if (existing) {
1441
+ return existing;
1442
+ }
1443
+ const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
1444
+ parserCache.set(cacheKey, parser);
1445
+ return parser;
1446
+ }
1447
+ function parseTSDocTags(node, file = "", options) {
1315
1448
  const constraints = [];
1316
1449
  const annotations = [];
1450
+ let displayName;
1451
+ let description;
1452
+ let placeholder;
1453
+ let displayNameProvenance;
1454
+ let descriptionProvenance;
1455
+ let placeholderProvenance;
1317
1456
  const sourceFile = node.getSourceFile();
1318
1457
  const sourceText = sourceFile.getFullText();
1319
1458
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1326,52 +1465,92 @@ function parseTSDocTags(node, file = "") {
1326
1465
  if (!commentText.startsWith("/**")) {
1327
1466
  continue;
1328
1467
  }
1329
- const parser = getParser();
1468
+ const parser = getParser(options);
1330
1469
  const parserContext = parser.parseRange(
1331
1470
  import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
1332
1471
  );
1333
1472
  const docComment = parserContext.docComment;
1334
1473
  for (const block of docComment.customBlocks) {
1335
1474
  const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
1336
- if (tagName === "displayName" || tagName === "description") {
1475
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1337
1476
  const text2 = extractBlockText(block).trim();
1338
1477
  if (text2 === "") continue;
1339
1478
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1340
1479
  if (tagName === "displayName") {
1480
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1481
+ displayName = text2;
1482
+ displayNameProvenance = provenance2;
1483
+ }
1484
+ } else if (tagName === "format") {
1341
1485
  annotations.push({
1342
1486
  kind: "annotation",
1343
- annotationKind: "displayName",
1487
+ annotationKind: "format",
1344
1488
  value: text2,
1345
1489
  provenance: provenance2
1346
1490
  });
1347
1491
  } else {
1348
- annotations.push({
1349
- kind: "annotation",
1350
- annotationKind: "description",
1351
- value: text2,
1352
- provenance: provenance2
1353
- });
1492
+ if (tagName === "description" && description === void 0) {
1493
+ description = text2;
1494
+ descriptionProvenance = provenance2;
1495
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1496
+ placeholder = text2;
1497
+ placeholderProvenance = provenance2;
1498
+ }
1354
1499
  }
1355
1500
  continue;
1356
1501
  }
1357
1502
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1358
1503
  const text = extractBlockText(block).trim();
1359
- if (text === "") continue;
1504
+ const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1505
+ if (text === "" && expectedType !== "boolean") continue;
1360
1506
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1361
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1507
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1362
1508
  if (constraintNode) {
1363
1509
  constraints.push(constraintNode);
1364
1510
  }
1365
1511
  }
1366
1512
  if (docComment.deprecatedBlock !== void 0) {
1513
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1367
1514
  annotations.push({
1368
1515
  kind: "annotation",
1369
1516
  annotationKind: "deprecated",
1517
+ ...message !== "" && { message },
1370
1518
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1371
1519
  });
1372
1520
  }
1521
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1522
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1523
+ if (remarks !== "") {
1524
+ description = remarks;
1525
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1526
+ }
1527
+ }
1373
1528
  }
1374
1529
  }
1530
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1531
+ annotations.push({
1532
+ kind: "annotation",
1533
+ annotationKind: "displayName",
1534
+ value: displayName,
1535
+ provenance: displayNameProvenance
1536
+ });
1537
+ }
1538
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1539
+ annotations.push({
1540
+ kind: "annotation",
1541
+ annotationKind: "description",
1542
+ value: description,
1543
+ provenance: descriptionProvenance
1544
+ });
1545
+ }
1546
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1547
+ annotations.push({
1548
+ kind: "annotation",
1549
+ annotationKind: "placeholder",
1550
+ value: placeholder,
1551
+ provenance: placeholderProvenance
1552
+ });
1553
+ }
1375
1554
  const jsDocTagsAll = ts2.getJSDocTags(node);
1376
1555
  for (const tag of jsDocTagsAll) {
1377
1556
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
@@ -1380,13 +1559,40 @@ function parseTSDocTags(node, file = "") {
1380
1559
  if (commentText === void 0 || commentText.trim() === "") continue;
1381
1560
  const text = commentText.trim();
1382
1561
  const provenance = provenanceForJSDocTag(tag, file);
1383
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1562
+ if (tagName === "defaultValue") {
1563
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1564
+ annotations.push(defaultValueNode);
1565
+ continue;
1566
+ }
1567
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1384
1568
  if (constraintNode) {
1385
1569
  constraints.push(constraintNode);
1386
1570
  }
1387
1571
  }
1388
1572
  return { constraints, annotations };
1389
1573
  }
1574
+ function extractDisplayNameMetadata(node) {
1575
+ let displayName;
1576
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1577
+ for (const tag of ts2.getJSDocTags(node)) {
1578
+ const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1579
+ if (tagName !== "displayName") continue;
1580
+ const commentText = getTagCommentText(tag);
1581
+ if (commentText === void 0) continue;
1582
+ const text = commentText.trim();
1583
+ if (text === "") continue;
1584
+ const memberTarget = parseMemberTargetDisplayName(text);
1585
+ if (memberTarget) {
1586
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1587
+ continue;
1588
+ }
1589
+ displayName ??= text;
1590
+ }
1591
+ return {
1592
+ ...displayName !== void 0 && { displayName },
1593
+ memberDisplayNames
1594
+ };
1595
+ }
1390
1596
  function extractPathTarget(text) {
1391
1597
  const trimmed = text.trimStart();
1392
1598
  const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
@@ -1414,7 +1620,11 @@ function extractPlainText(node) {
1414
1620
  }
1415
1621
  return result;
1416
1622
  }
1417
- function parseConstraintValue(tagName, text, provenance) {
1623
+ function parseConstraintValue(tagName, text, provenance, options) {
1624
+ const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
1625
+ if (customConstraint) {
1626
+ return customConstraint;
1627
+ }
1418
1628
  if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1419
1629
  return null;
1420
1630
  }
@@ -1449,7 +1659,45 @@ function parseConstraintValue(tagName, text, provenance) {
1449
1659
  }
1450
1660
  return null;
1451
1661
  }
1662
+ if (expectedType === "boolean") {
1663
+ const trimmed = effectiveText.trim();
1664
+ if (trimmed !== "" && trimmed !== "true") {
1665
+ return null;
1666
+ }
1667
+ if (tagName === "uniqueItems") {
1668
+ return {
1669
+ kind: "constraint",
1670
+ constraintKind: "uniqueItems",
1671
+ value: true,
1672
+ ...path4 && { path: path4 },
1673
+ provenance
1674
+ };
1675
+ }
1676
+ return null;
1677
+ }
1452
1678
  if (expectedType === "json") {
1679
+ if (tagName === "const") {
1680
+ const trimmedText = effectiveText.trim();
1681
+ if (trimmedText === "") return null;
1682
+ try {
1683
+ const parsed2 = JSON.parse(trimmedText);
1684
+ return {
1685
+ kind: "constraint",
1686
+ constraintKind: "const",
1687
+ value: parsed2,
1688
+ ...path4 && { path: path4 },
1689
+ provenance
1690
+ };
1691
+ } catch {
1692
+ return {
1693
+ kind: "constraint",
1694
+ constraintKind: "const",
1695
+ value: trimmedText,
1696
+ ...path4 && { path: path4 },
1697
+ provenance
1698
+ };
1699
+ }
1700
+ }
1453
1701
  const parsed = tryParseJson(effectiveText);
1454
1702
  if (!Array.isArray(parsed)) {
1455
1703
  return null;
@@ -1481,6 +1729,111 @@ function parseConstraintValue(tagName, text, provenance) {
1481
1729
  provenance
1482
1730
  };
1483
1731
  }
1732
+ function parseExtensionConstraintValue(tagName, text, provenance, options) {
1733
+ const pathResult = extractPathTarget(text);
1734
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1735
+ const path4 = pathResult?.path;
1736
+ const registry = options?.extensionRegistry;
1737
+ if (registry === void 0) {
1738
+ return null;
1739
+ }
1740
+ const directTag = registry.findConstraintTag(tagName);
1741
+ if (directTag !== void 0) {
1742
+ return makeCustomConstraintNode(
1743
+ directTag.extensionId,
1744
+ directTag.registration.constraintName,
1745
+ directTag.registration.parseValue(effectiveText),
1746
+ provenance,
1747
+ path4,
1748
+ registry
1749
+ );
1750
+ }
1751
+ if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1752
+ return null;
1753
+ }
1754
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
1755
+ if (broadenedTypeId === void 0) {
1756
+ return null;
1757
+ }
1758
+ const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
1759
+ if (broadened === void 0) {
1760
+ return null;
1761
+ }
1762
+ return makeCustomConstraintNode(
1763
+ broadened.extensionId,
1764
+ broadened.registration.constraintName,
1765
+ broadened.registration.parseValue(effectiveText),
1766
+ provenance,
1767
+ path4,
1768
+ registry
1769
+ );
1770
+ }
1771
+ function getBroadenedCustomTypeId(fieldType) {
1772
+ if (fieldType?.kind === "custom") {
1773
+ return fieldType.typeId;
1774
+ }
1775
+ if (fieldType?.kind !== "union") {
1776
+ return void 0;
1777
+ }
1778
+ const customMembers = fieldType.members.filter(
1779
+ (member) => member.kind === "custom"
1780
+ );
1781
+ if (customMembers.length !== 1) {
1782
+ return void 0;
1783
+ }
1784
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
1785
+ const allOtherMembersAreNull = nonCustomMembers.every(
1786
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
1787
+ );
1788
+ const customMember = customMembers[0];
1789
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
1790
+ }
1791
+ function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path4, registry) {
1792
+ const constraintId = `${extensionId}/${constraintName}`;
1793
+ const registration = registry.findConstraint(constraintId);
1794
+ if (registration === void 0) {
1795
+ throw new Error(
1796
+ `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
1797
+ );
1798
+ }
1799
+ return {
1800
+ kind: "constraint",
1801
+ constraintKind: "custom",
1802
+ constraintId,
1803
+ payload,
1804
+ compositionRule: registration.compositionRule,
1805
+ ...path4 && { path: path4 },
1806
+ provenance
1807
+ };
1808
+ }
1809
+ function parseDefaultValueValue(text, provenance) {
1810
+ const trimmed = text.trim();
1811
+ let value;
1812
+ if (trimmed === "null") {
1813
+ value = null;
1814
+ } else if (trimmed === "true") {
1815
+ value = true;
1816
+ } else if (trimmed === "false") {
1817
+ value = false;
1818
+ } else {
1819
+ const parsed = tryParseJson(trimmed);
1820
+ value = parsed !== null ? parsed : trimmed;
1821
+ }
1822
+ return {
1823
+ kind: "annotation",
1824
+ annotationKind: "defaultValue",
1825
+ value,
1826
+ provenance
1827
+ };
1828
+ }
1829
+ function isMemberTargetDisplayName(text) {
1830
+ return parseMemberTargetDisplayName(text) !== null;
1831
+ }
1832
+ function parseMemberTargetDisplayName(text) {
1833
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1834
+ if (!match?.[1] || !match[2]) return null;
1835
+ return { target: match[1], label: match[2].trim() };
1836
+ }
1484
1837
  function provenanceForComment(range, sourceFile, file, tagName) {
1485
1838
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1486
1839
  return {
@@ -1511,7 +1864,7 @@ function getTagCommentText(tag) {
1511
1864
  }
1512
1865
  return ts2.getTextOfJSDocComment(tag.comment);
1513
1866
  }
1514
- var ts2, import_tsdoc, import_core3, NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, sharedParser;
1867
+ var ts2, import_tsdoc, import_core3, NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, parserCache;
1515
1868
  var init_tsdoc_parser = __esm({
1516
1869
  "src/analyzer/tsdoc-parser.ts"() {
1517
1870
  "use strict";
@@ -1532,17 +1885,18 @@ var init_tsdoc_parser = __esm({
1532
1885
  minItems: "minItems",
1533
1886
  maxItems: "maxItems"
1534
1887
  };
1535
- TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1888
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1889
+ parserCache = /* @__PURE__ */ new Map();
1536
1890
  }
1537
1891
  });
1538
1892
 
1539
1893
  // src/analyzer/jsdoc-constraints.ts
1540
- function extractJSDocConstraintNodes(node, file = "") {
1541
- const result = parseTSDocTags(node, file);
1894
+ function extractJSDocConstraintNodes(node, file = "", options) {
1895
+ const result = parseTSDocTags(node, file, options);
1542
1896
  return [...result.constraints];
1543
1897
  }
1544
- function extractJSDocAnnotationNodes(node, file = "") {
1545
- const result = parseTSDocTags(node, file);
1898
+ function extractJSDocAnnotationNodes(node, file = "", options) {
1899
+ const result = parseTSDocTags(node, file, options);
1546
1900
  return [...result.annotations];
1547
1901
  }
1548
1902
  function extractDefaultValueAnnotation(initializer, file = "") {
@@ -1594,17 +1948,38 @@ function isObjectType(type) {
1594
1948
  function isTypeReference(type) {
1595
1949
  return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1596
1950
  }
1597
- function analyzeClassToIR(classDecl, checker, file = "") {
1951
+ function makeParseOptions(extensionRegistry, fieldType) {
1952
+ if (extensionRegistry === void 0 && fieldType === void 0) {
1953
+ return void 0;
1954
+ }
1955
+ return {
1956
+ ...extensionRegistry !== void 0 && { extensionRegistry },
1957
+ ...fieldType !== void 0 && { fieldType }
1958
+ };
1959
+ }
1960
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1598
1961
  const name = classDecl.name?.text ?? "AnonymousClass";
1599
1962
  const fields = [];
1600
1963
  const fieldLayouts = [];
1601
1964
  const typeRegistry = {};
1965
+ const annotations = extractJSDocAnnotationNodes(
1966
+ classDecl,
1967
+ file,
1968
+ makeParseOptions(extensionRegistry)
1969
+ );
1602
1970
  const visiting = /* @__PURE__ */ new Set();
1603
1971
  const instanceMethods = [];
1604
1972
  const staticMethods = [];
1605
1973
  for (const member of classDecl.members) {
1606
1974
  if (ts4.isPropertyDeclaration(member)) {
1607
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1975
+ const fieldNode = analyzeFieldToIR(
1976
+ member,
1977
+ checker,
1978
+ file,
1979
+ typeRegistry,
1980
+ visiting,
1981
+ extensionRegistry
1982
+ );
1608
1983
  if (fieldNode) {
1609
1984
  fields.push(fieldNode);
1610
1985
  fieldLayouts.push({});
@@ -1621,25 +1996,53 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1621
1996
  }
1622
1997
  }
1623
1998
  }
1624
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1999
+ return {
2000
+ name,
2001
+ fields,
2002
+ fieldLayouts,
2003
+ typeRegistry,
2004
+ ...annotations.length > 0 && { annotations },
2005
+ instanceMethods,
2006
+ staticMethods
2007
+ };
1625
2008
  }
1626
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
2009
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
1627
2010
  const name = interfaceDecl.name.text;
1628
2011
  const fields = [];
1629
2012
  const typeRegistry = {};
2013
+ const annotations = extractJSDocAnnotationNodes(
2014
+ interfaceDecl,
2015
+ file,
2016
+ makeParseOptions(extensionRegistry)
2017
+ );
1630
2018
  const visiting = /* @__PURE__ */ new Set();
1631
2019
  for (const member of interfaceDecl.members) {
1632
2020
  if (ts4.isPropertySignature(member)) {
1633
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2021
+ const fieldNode = analyzeInterfacePropertyToIR(
2022
+ member,
2023
+ checker,
2024
+ file,
2025
+ typeRegistry,
2026
+ visiting,
2027
+ extensionRegistry
2028
+ );
1634
2029
  if (fieldNode) {
1635
2030
  fields.push(fieldNode);
1636
2031
  }
1637
2032
  }
1638
2033
  }
1639
2034
  const fieldLayouts = fields.map(() => ({}));
1640
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
2035
+ return {
2036
+ name,
2037
+ fields,
2038
+ fieldLayouts,
2039
+ typeRegistry,
2040
+ ...annotations.length > 0 && { annotations },
2041
+ instanceMethods: [],
2042
+ staticMethods: []
2043
+ };
1641
2044
  }
1642
- function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
2045
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1643
2046
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1644
2047
  const sourceFile = typeAlias.getSourceFile();
1645
2048
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -1652,10 +2055,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1652
2055
  const name = typeAlias.name.text;
1653
2056
  const fields = [];
1654
2057
  const typeRegistry = {};
2058
+ const annotations = extractJSDocAnnotationNodes(
2059
+ typeAlias,
2060
+ file,
2061
+ makeParseOptions(extensionRegistry)
2062
+ );
1655
2063
  const visiting = /* @__PURE__ */ new Set();
1656
2064
  for (const member of typeAlias.type.members) {
1657
2065
  if (ts4.isPropertySignature(member)) {
1658
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2066
+ const fieldNode = analyzeInterfacePropertyToIR(
2067
+ member,
2068
+ checker,
2069
+ file,
2070
+ typeRegistry,
2071
+ visiting,
2072
+ extensionRegistry
2073
+ );
1659
2074
  if (fieldNode) {
1660
2075
  fields.push(fieldNode);
1661
2076
  }
@@ -1668,12 +2083,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1668
2083
  fields,
1669
2084
  fieldLayouts: fields.map(() => ({})),
1670
2085
  typeRegistry,
2086
+ ...annotations.length > 0 && { annotations },
1671
2087
  instanceMethods: [],
1672
2088
  staticMethods: []
1673
2089
  }
1674
2090
  };
1675
2091
  }
1676
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
2092
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1677
2093
  if (!ts4.isIdentifier(prop.name)) {
1678
2094
  return null;
1679
2095
  }
@@ -1681,16 +2097,28 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1681
2097
  const tsType = checker.getTypeAtLocation(prop);
1682
2098
  const optional = prop.questionToken !== void 0;
1683
2099
  const provenance = provenanceForNode(prop, file);
1684
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
2100
+ let type = resolveTypeNode(
2101
+ tsType,
2102
+ checker,
2103
+ file,
2104
+ typeRegistry,
2105
+ visiting,
2106
+ prop,
2107
+ extensionRegistry
2108
+ );
1685
2109
  const constraints = [];
1686
2110
  if (prop.type) {
1687
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2111
+ constraints.push(
2112
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2113
+ );
1688
2114
  }
1689
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2115
+ constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
1690
2116
  let annotations = [];
1691
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2117
+ annotations.push(
2118
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2119
+ );
1692
2120
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1693
- if (defaultAnnotation) {
2121
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1694
2122
  annotations.push(defaultAnnotation);
1695
2123
  }
1696
2124
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
@@ -1704,7 +2132,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1704
2132
  provenance
1705
2133
  };
1706
2134
  }
1707
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
2135
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1708
2136
  if (!ts4.isIdentifier(prop.name)) {
1709
2137
  return null;
1710
2138
  }
@@ -1712,14 +2140,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1712
2140
  const tsType = checker.getTypeAtLocation(prop);
1713
2141
  const optional = prop.questionToken !== void 0;
1714
2142
  const provenance = provenanceForNode(prop, file);
1715
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
2143
+ let type = resolveTypeNode(
2144
+ tsType,
2145
+ checker,
2146
+ file,
2147
+ typeRegistry,
2148
+ visiting,
2149
+ prop,
2150
+ extensionRegistry
2151
+ );
1716
2152
  const constraints = [];
1717
2153
  if (prop.type) {
1718
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2154
+ constraints.push(
2155
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2156
+ );
1719
2157
  }
1720
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2158
+ constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
1721
2159
  let annotations = [];
1722
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2160
+ annotations.push(
2161
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2162
+ );
1723
2163
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1724
2164
  return {
1725
2165
  kind: "field",
@@ -1793,7 +2233,66 @@ function parseEnumMemberDisplayName(value) {
1793
2233
  if (label === "") return null;
1794
2234
  return { value: match[1], label };
1795
2235
  }
1796
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
2236
+ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
2237
+ if (sourceNode === void 0 || extensionRegistry === void 0) {
2238
+ return null;
2239
+ }
2240
+ const typeNode = extractTypeNodeFromSource(sourceNode);
2241
+ if (typeNode === void 0) {
2242
+ return null;
2243
+ }
2244
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
2245
+ }
2246
+ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
2247
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
2248
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
2249
+ }
2250
+ const typeName = getTypeNodeRegistrationName(typeNode);
2251
+ if (typeName === null) {
2252
+ return null;
2253
+ }
2254
+ const registration = extensionRegistry.findTypeByName(typeName);
2255
+ if (registration !== void 0) {
2256
+ return {
2257
+ kind: "custom",
2258
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
2259
+ payload: null
2260
+ };
2261
+ }
2262
+ if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
2263
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
2264
+ if (aliasDecl !== void 0) {
2265
+ return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
2266
+ }
2267
+ }
2268
+ return null;
2269
+ }
2270
+ function extractTypeNodeFromSource(sourceNode) {
2271
+ if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
2272
+ return sourceNode.type;
2273
+ }
2274
+ if (ts4.isTypeNode(sourceNode)) {
2275
+ return sourceNode;
2276
+ }
2277
+ return void 0;
2278
+ }
2279
+ function getTypeNodeRegistrationName(typeNode) {
2280
+ if (ts4.isTypeReferenceNode(typeNode)) {
2281
+ return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
2282
+ }
2283
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
2284
+ return getTypeNodeRegistrationName(typeNode.type);
2285
+ }
2286
+ if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
2287
+ return typeNode.getText();
2288
+ }
2289
+ return null;
2290
+ }
2291
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2292
+ const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2293
+ if (customType) {
2294
+ return customType;
2295
+ }
1797
2296
  if (type.flags & ts4.TypeFlags.String) {
1798
2297
  return { kind: "primitive", primitiveKind: "string" };
1799
2298
  }
@@ -1822,88 +2321,162 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1822
2321
  };
1823
2322
  }
1824
2323
  if (type.isUnion()) {
1825
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
2324
+ return resolveUnionType(
2325
+ type,
2326
+ checker,
2327
+ file,
2328
+ typeRegistry,
2329
+ visiting,
2330
+ sourceNode,
2331
+ extensionRegistry
2332
+ );
1826
2333
  }
1827
2334
  if (checker.isArrayType(type)) {
1828
- return resolveArrayType(type, checker, file, typeRegistry, visiting);
2335
+ return resolveArrayType(
2336
+ type,
2337
+ checker,
2338
+ file,
2339
+ typeRegistry,
2340
+ visiting,
2341
+ sourceNode,
2342
+ extensionRegistry
2343
+ );
1829
2344
  }
1830
2345
  if (isObjectType(type)) {
1831
- return resolveObjectType(type, checker, file, typeRegistry, visiting);
2346
+ return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
1832
2347
  }
1833
2348
  return { kind: "primitive", primitiveKind: "string" };
1834
2349
  }
1835
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
2350
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2351
+ const typeName = getNamedTypeName(type);
2352
+ const namedDecl = getNamedTypeDeclaration(type);
2353
+ if (typeName && typeName in typeRegistry) {
2354
+ return { kind: "reference", name: typeName, typeArguments: [] };
2355
+ }
1836
2356
  const allTypes = type.types;
2357
+ const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
2358
+ const nonNullSourceNodes = unionMemberTypeNodes.filter(
2359
+ (memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
2360
+ );
1837
2361
  const nonNullTypes = allTypes.filter(
1838
- (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
2362
+ (memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1839
2363
  );
2364
+ const nonNullMembers = nonNullTypes.map((memberType, index) => ({
2365
+ memberType,
2366
+ sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
2367
+ }));
1840
2368
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2369
+ const memberDisplayNames = /* @__PURE__ */ new Map();
2370
+ if (namedDecl) {
2371
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
2372
+ memberDisplayNames.set(value, label);
2373
+ }
2374
+ }
2375
+ if (sourceNode) {
2376
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
2377
+ memberDisplayNames.set(value, label);
2378
+ }
2379
+ }
2380
+ const registerNamed = (result) => {
2381
+ if (!typeName) {
2382
+ return result;
2383
+ }
2384
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2385
+ typeRegistry[typeName] = {
2386
+ name: typeName,
2387
+ type: result,
2388
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2389
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
2390
+ };
2391
+ return { kind: "reference", name: typeName, typeArguments: [] };
2392
+ };
2393
+ const applyMemberLabels = (members2) => members2.map((value) => {
2394
+ const displayName = memberDisplayNames.get(String(value));
2395
+ return displayName !== void 0 ? { value, displayName } : { value };
2396
+ });
1841
2397
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1842
2398
  if (isBooleanUnion2) {
1843
2399
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1844
- if (hasNull) {
1845
- return {
1846
- kind: "union",
1847
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1848
- };
1849
- }
1850
- return boolNode;
2400
+ const result = hasNull ? {
2401
+ kind: "union",
2402
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
2403
+ } : boolNode;
2404
+ return registerNamed(result);
1851
2405
  }
1852
2406
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1853
2407
  if (allStringLiterals && nonNullTypes.length > 0) {
1854
2408
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1855
2409
  const enumNode = {
1856
2410
  kind: "enum",
1857
- members: stringTypes.map((t) => ({ value: t.value }))
2411
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1858
2412
  };
1859
- if (hasNull) {
1860
- return {
1861
- kind: "union",
1862
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1863
- };
1864
- }
1865
- return enumNode;
2413
+ const result = hasNull ? {
2414
+ kind: "union",
2415
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2416
+ } : enumNode;
2417
+ return registerNamed(result);
1866
2418
  }
1867
2419
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1868
2420
  if (allNumberLiterals && nonNullTypes.length > 0) {
1869
2421
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1870
2422
  const enumNode = {
1871
2423
  kind: "enum",
1872
- members: numberTypes.map((t) => ({ value: t.value }))
2424
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1873
2425
  };
1874
- if (hasNull) {
1875
- return {
1876
- kind: "union",
1877
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1878
- };
1879
- }
1880
- return enumNode;
1881
- }
1882
- if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1883
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1884
- if (hasNull) {
1885
- return {
1886
- kind: "union",
1887
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1888
- };
1889
- }
1890
- return inner;
1891
- }
1892
- const members = nonNullTypes.map(
1893
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2426
+ const result = hasNull ? {
2427
+ kind: "union",
2428
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2429
+ } : enumNode;
2430
+ return registerNamed(result);
2431
+ }
2432
+ if (nonNullMembers.length === 1 && nonNullMembers[0]) {
2433
+ const inner = resolveTypeNode(
2434
+ nonNullMembers[0].memberType,
2435
+ checker,
2436
+ file,
2437
+ typeRegistry,
2438
+ visiting,
2439
+ nonNullMembers[0].sourceNode ?? sourceNode,
2440
+ extensionRegistry
2441
+ );
2442
+ const result = hasNull ? {
2443
+ kind: "union",
2444
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2445
+ } : inner;
2446
+ return registerNamed(result);
2447
+ }
2448
+ const members = nonNullMembers.map(
2449
+ ({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
2450
+ memberType,
2451
+ checker,
2452
+ file,
2453
+ typeRegistry,
2454
+ visiting,
2455
+ memberSourceNode ?? sourceNode,
2456
+ extensionRegistry
2457
+ )
1894
2458
  );
1895
2459
  if (hasNull) {
1896
2460
  members.push({ kind: "primitive", primitiveKind: "null" });
1897
2461
  }
1898
- return { kind: "union", members };
2462
+ return registerNamed({ kind: "union", members });
1899
2463
  }
1900
- function resolveArrayType(type, checker, file, typeRegistry, visiting) {
2464
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1901
2465
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
1902
2466
  const elementType = typeArgs?.[0];
1903
- const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
2467
+ const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
2468
+ const items = elementType ? resolveTypeNode(
2469
+ elementType,
2470
+ checker,
2471
+ file,
2472
+ typeRegistry,
2473
+ visiting,
2474
+ elementSourceNode,
2475
+ extensionRegistry
2476
+ ) : { kind: "primitive", primitiveKind: "string" };
1904
2477
  return { kind: "array", items };
1905
2478
  }
1906
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2479
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1907
2480
  if (type.getProperties().length > 0) {
1908
2481
  return null;
1909
2482
  }
@@ -1911,39 +2484,123 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1911
2484
  if (!indexInfo) {
1912
2485
  return null;
1913
2486
  }
1914
- if (visiting.has(type)) {
1915
- return null;
1916
- }
1917
- visiting.add(type);
1918
- try {
1919
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1920
- return { kind: "record", valueType };
1921
- } finally {
1922
- visiting.delete(type);
1923
- }
2487
+ const valueType = resolveTypeNode(
2488
+ indexInfo.type,
2489
+ checker,
2490
+ file,
2491
+ typeRegistry,
2492
+ visiting,
2493
+ void 0,
2494
+ extensionRegistry
2495
+ );
2496
+ return { kind: "record", valueType };
1924
2497
  }
1925
- function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1926
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1927
- if (recordNode) {
1928
- return recordNode;
2498
+ function typeNodeContainsReference(type, targetName) {
2499
+ switch (type.kind) {
2500
+ case "reference":
2501
+ return type.name === targetName;
2502
+ case "array":
2503
+ return typeNodeContainsReference(type.items, targetName);
2504
+ case "record":
2505
+ return typeNodeContainsReference(type.valueType, targetName);
2506
+ case "union":
2507
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2508
+ case "object":
2509
+ return type.properties.some(
2510
+ (property) => typeNodeContainsReference(property.type, targetName)
2511
+ );
2512
+ case "primitive":
2513
+ case "enum":
2514
+ case "dynamic":
2515
+ case "custom":
2516
+ return false;
2517
+ default: {
2518
+ const _exhaustive = type;
2519
+ return _exhaustive;
2520
+ }
1929
2521
  }
2522
+ }
2523
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2524
+ const typeName = getNamedTypeName(type);
2525
+ const namedTypeName = typeName ?? void 0;
2526
+ const namedDecl = getNamedTypeDeclaration(type);
2527
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2528
+ const clearNamedTypeRegistration = () => {
2529
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2530
+ return;
2531
+ }
2532
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2533
+ };
1930
2534
  if (visiting.has(type)) {
2535
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2536
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2537
+ }
1931
2538
  return { kind: "object", properties: [], additionalProperties: false };
1932
2539
  }
2540
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2541
+ typeRegistry[namedTypeName] = {
2542
+ name: namedTypeName,
2543
+ type: RESOLVING_TYPE_PLACEHOLDER,
2544
+ provenance: provenanceForDeclaration(namedDecl, file)
2545
+ };
2546
+ }
1933
2547
  visiting.add(type);
1934
- const typeName = getNamedTypeName(type);
1935
- if (typeName && typeName in typeRegistry) {
2548
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2549
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2550
+ visiting.delete(type);
2551
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2552
+ }
2553
+ }
2554
+ const recordNode = tryResolveRecordType(
2555
+ type,
2556
+ checker,
2557
+ file,
2558
+ typeRegistry,
2559
+ visiting,
2560
+ extensionRegistry
2561
+ );
2562
+ if (recordNode) {
1936
2563
  visiting.delete(type);
1937
- return { kind: "reference", name: typeName, typeArguments: [] };
2564
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2565
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2566
+ if (!isRecursiveRecord) {
2567
+ clearNamedTypeRegistration();
2568
+ return recordNode;
2569
+ }
2570
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2571
+ typeRegistry[namedTypeName] = {
2572
+ name: namedTypeName,
2573
+ type: recordNode,
2574
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2575
+ provenance: provenanceForDeclaration(namedDecl, file)
2576
+ };
2577
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2578
+ }
2579
+ return recordNode;
1938
2580
  }
1939
2581
  const properties = [];
1940
- const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
2582
+ const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
2583
+ type,
2584
+ checker,
2585
+ file,
2586
+ typeRegistry,
2587
+ visiting,
2588
+ extensionRegistry
2589
+ );
1941
2590
  for (const prop of type.getProperties()) {
1942
2591
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1943
2592
  if (!declaration) continue;
1944
2593
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1945
2594
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1946
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2595
+ const propTypeNode = resolveTypeNode(
2596
+ propType,
2597
+ checker,
2598
+ file,
2599
+ typeRegistry,
2600
+ visiting,
2601
+ declaration,
2602
+ extensionRegistry
2603
+ );
1947
2604
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1948
2605
  properties.push({
1949
2606
  name: prop.name,
@@ -1960,17 +2617,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1960
2617
  properties,
1961
2618
  additionalProperties: true
1962
2619
  };
1963
- if (typeName) {
1964
- typeRegistry[typeName] = {
1965
- name: typeName,
2620
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2621
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2622
+ typeRegistry[namedTypeName] = {
2623
+ name: namedTypeName,
1966
2624
  type: objectNode,
1967
- provenance: provenanceForFile(file)
2625
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2626
+ provenance: provenanceForDeclaration(namedDecl, file)
1968
2627
  };
1969
- return { kind: "reference", name: typeName, typeArguments: [] };
2628
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1970
2629
  }
1971
2630
  return objectNode;
1972
2631
  }
1973
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
2632
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1974
2633
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
1975
2634
  (s) => s?.declarations != null && s.declarations.length > 0
1976
2635
  );
@@ -1982,7 +2641,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1982
2641
  const map = /* @__PURE__ */ new Map();
1983
2642
  for (const member of classDecl.members) {
1984
2643
  if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
1985
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
2644
+ const fieldNode = analyzeFieldToIR(
2645
+ member,
2646
+ checker,
2647
+ file,
2648
+ typeRegistry,
2649
+ visiting,
2650
+ extensionRegistry
2651
+ );
1986
2652
  if (fieldNode) {
1987
2653
  map.set(fieldNode.name, {
1988
2654
  constraints: [...fieldNode.constraints],
@@ -1996,7 +2662,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1996
2662
  }
1997
2663
  const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
1998
2664
  if (interfaceDecl) {
1999
- return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
2665
+ return buildFieldNodeInfoMap(
2666
+ interfaceDecl.members,
2667
+ checker,
2668
+ file,
2669
+ typeRegistry,
2670
+ visiting,
2671
+ extensionRegistry
2672
+ );
2000
2673
  }
2001
2674
  const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
2002
2675
  if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
@@ -2005,17 +2678,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2005
2678
  checker,
2006
2679
  file,
2007
2680
  typeRegistry,
2008
- visiting
2681
+ visiting,
2682
+ extensionRegistry
2009
2683
  );
2010
2684
  }
2011
2685
  }
2012
2686
  return null;
2013
2687
  }
2014
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2688
+ function extractArrayElementTypeNode(sourceNode, checker) {
2689
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2690
+ if (typeNode === void 0) {
2691
+ return void 0;
2692
+ }
2693
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2694
+ if (ts4.isArrayTypeNode(resolvedTypeNode)) {
2695
+ return resolvedTypeNode.elementType;
2696
+ }
2697
+ if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
2698
+ return resolvedTypeNode.typeArguments[0];
2699
+ }
2700
+ return void 0;
2701
+ }
2702
+ function extractUnionMemberTypeNodes(sourceNode, checker) {
2703
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2704
+ if (!typeNode) {
2705
+ return [];
2706
+ }
2707
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2708
+ return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
2709
+ }
2710
+ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
2711
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
2712
+ return resolveAliasedTypeNode(typeNode.type, checker, visited);
2713
+ }
2714
+ if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
2715
+ return typeNode;
2716
+ }
2717
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2718
+ const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
2719
+ if (aliasDecl === void 0 || visited.has(aliasDecl)) {
2720
+ return typeNode;
2721
+ }
2722
+ visited.add(aliasDecl);
2723
+ return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
2724
+ }
2725
+ function isNullishTypeNode(typeNode) {
2726
+ if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
2727
+ return true;
2728
+ }
2729
+ return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
2730
+ }
2731
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
2015
2732
  const map = /* @__PURE__ */ new Map();
2016
2733
  for (const member of members) {
2017
2734
  if (ts4.isPropertySignature(member)) {
2018
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2735
+ const fieldNode = analyzeInterfacePropertyToIR(
2736
+ member,
2737
+ checker,
2738
+ file,
2739
+ typeRegistry,
2740
+ visiting,
2741
+ extensionRegistry
2742
+ );
2019
2743
  if (fieldNode) {
2020
2744
  map.set(fieldNode.name, {
2021
2745
  constraints: [...fieldNode.constraints],
@@ -2027,7 +2751,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2027
2751
  }
2028
2752
  return map;
2029
2753
  }
2030
- function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2754
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
2031
2755
  if (!ts4.isTypeReferenceNode(typeNode)) return [];
2032
2756
  if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
2033
2757
  const aliasName = typeNode.typeName.getText();
@@ -2040,8 +2764,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2040
2764
  const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
2041
2765
  if (!aliasDecl) return [];
2042
2766
  if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
2043
- const constraints = extractJSDocConstraintNodes(aliasDecl, file);
2044
- constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
2767
+ const aliasFieldType = resolveTypeNode(
2768
+ checker.getTypeAtLocation(aliasDecl.type),
2769
+ checker,
2770
+ file,
2771
+ {},
2772
+ /* @__PURE__ */ new Set(),
2773
+ aliasDecl.type,
2774
+ extensionRegistry
2775
+ );
2776
+ const constraints = extractJSDocConstraintNodes(
2777
+ aliasDecl,
2778
+ file,
2779
+ makeParseOptions(extensionRegistry, aliasFieldType)
2780
+ );
2781
+ constraints.push(
2782
+ ...extractTypeAliasConstraintNodes(
2783
+ aliasDecl.type,
2784
+ checker,
2785
+ file,
2786
+ extensionRegistry,
2787
+ depth + 1
2788
+ )
2789
+ );
2045
2790
  return constraints;
2046
2791
  }
2047
2792
  function provenanceForNode(node, file) {
@@ -2057,6 +2802,12 @@ function provenanceForNode(node, file) {
2057
2802
  function provenanceForFile(file) {
2058
2803
  return { surface: "tsdoc", file, line: 0, column: 0 };
2059
2804
  }
2805
+ function provenanceForDeclaration(node, file) {
2806
+ if (!node) {
2807
+ return provenanceForFile(file);
2808
+ }
2809
+ return provenanceForNode(node, file);
2810
+ }
2060
2811
  function getNamedTypeName(type) {
2061
2812
  const symbol = type.getSymbol();
2062
2813
  if (symbol?.declarations) {
@@ -2075,6 +2826,20 @@ function getNamedTypeName(type) {
2075
2826
  }
2076
2827
  return null;
2077
2828
  }
2829
+ function getNamedTypeDeclaration(type) {
2830
+ const symbol = type.getSymbol();
2831
+ if (symbol?.declarations) {
2832
+ const decl = symbol.declarations[0];
2833
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2834
+ return decl;
2835
+ }
2836
+ }
2837
+ const aliasSymbol = type.aliasSymbol;
2838
+ if (aliasSymbol?.declarations) {
2839
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2840
+ }
2841
+ return void 0;
2842
+ }
2078
2843
  function analyzeMethod(method, checker) {
2079
2844
  if (!ts4.isIdentifier(method.name)) {
2080
2845
  return null;
@@ -2115,21 +2880,27 @@ function detectFormSpecReference(typeNode) {
2115
2880
  }
2116
2881
  return null;
2117
2882
  }
2118
- var ts4, MAX_ALIAS_CHAIN_DEPTH;
2883
+ var ts4, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
2119
2884
  var init_class_analyzer = __esm({
2120
2885
  "src/analyzer/class-analyzer.ts"() {
2121
2886
  "use strict";
2122
2887
  ts4 = __toESM(require("typescript"), 1);
2123
2888
  init_jsdoc_constraints();
2889
+ init_tsdoc_parser();
2890
+ RESOLVING_TYPE_PLACEHOLDER = {
2891
+ kind: "object",
2892
+ properties: [],
2893
+ additionalProperties: true
2894
+ };
2124
2895
  MAX_ALIAS_CHAIN_DEPTH = 8;
2125
2896
  }
2126
2897
  });
2127
2898
 
2128
2899
  // src/generators/class-schema.ts
2129
- function generateClassSchemas(analysis, source) {
2900
+ function generateClassSchemas(analysis, source, options) {
2130
2901
  const ir = canonicalizeTSDoc(analysis, source);
2131
2902
  return {
2132
- jsonSchema: generateJsonSchemaFromIR(ir),
2903
+ jsonSchema: generateJsonSchemaFromIR(ir, options),
2133
2904
  uiSchema: generateUiSchemaFromIR(ir)
2134
2905
  };
2135
2906
  }
@@ -2139,27 +2910,54 @@ function generateSchemasFromClass(options) {
2139
2910
  if (!classDecl) {
2140
2911
  throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
2141
2912
  }
2142
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2143
- return generateClassSchemas(analysis, { file: options.filePath });
2913
+ const analysis = analyzeClassToIR(
2914
+ classDecl,
2915
+ ctx.checker,
2916
+ options.filePath,
2917
+ options.extensionRegistry
2918
+ );
2919
+ return generateClassSchemas(
2920
+ analysis,
2921
+ { file: options.filePath },
2922
+ {
2923
+ extensionRegistry: options.extensionRegistry,
2924
+ vendorPrefix: options.vendorPrefix
2925
+ }
2926
+ );
2144
2927
  }
2145
2928
  function generateSchemas(options) {
2146
2929
  const ctx = createProgramContext(options.filePath);
2147
2930
  const source = { file: options.filePath };
2148
2931
  const classDecl = findClassByName(ctx.sourceFile, options.typeName);
2149
2932
  if (classDecl) {
2150
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2151
- return generateClassSchemas(analysis, source);
2933
+ const analysis = analyzeClassToIR(
2934
+ classDecl,
2935
+ ctx.checker,
2936
+ options.filePath,
2937
+ options.extensionRegistry
2938
+ );
2939
+ return generateClassSchemas(analysis, source, options);
2152
2940
  }
2153
2941
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
2154
2942
  if (interfaceDecl) {
2155
- const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
2156
- return generateClassSchemas(analysis, source);
2943
+ const analysis = analyzeInterfaceToIR(
2944
+ interfaceDecl,
2945
+ ctx.checker,
2946
+ options.filePath,
2947
+ options.extensionRegistry
2948
+ );
2949
+ return generateClassSchemas(analysis, source, options);
2157
2950
  }
2158
2951
  const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
2159
2952
  if (typeAlias) {
2160
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
2953
+ const result = analyzeTypeAliasToIR(
2954
+ typeAlias,
2955
+ ctx.checker,
2956
+ options.filePath,
2957
+ options.extensionRegistry
2958
+ );
2161
2959
  if (result.ok) {
2162
- return generateClassSchemas(result.analysis, source);
2960
+ return generateClassSchemas(result.analysis, source, options);
2163
2961
  }
2164
2962
  throw new Error(result.error);
2165
2963
  }
@@ -2178,10 +2976,220 @@ var init_class_schema = __esm({
2178
2976
  }
2179
2977
  });
2180
2978
 
2979
+ // src/generators/mixed-authoring.ts
2980
+ function buildMixedAuthoringSchemas(options) {
2981
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2982
+ const analysis = analyzeNamedType(filePath, typeName, schemaOptions.extensionRegistry);
2983
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2984
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2985
+ return {
2986
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2987
+ uiSchema: generateUiSchemaFromIR(ir)
2988
+ };
2989
+ }
2990
+ function analyzeNamedType(filePath, typeName, extensionRegistry) {
2991
+ const ctx = createProgramContext(filePath);
2992
+ const source = { file: filePath };
2993
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2994
+ if (classDecl !== null) {
2995
+ return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
2996
+ }
2997
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2998
+ if (interfaceDecl !== null) {
2999
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
3000
+ }
3001
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3002
+ if (typeAlias !== null) {
3003
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
3004
+ if (result.ok) {
3005
+ return result.analysis;
3006
+ }
3007
+ throw new Error(result.error);
3008
+ }
3009
+ throw new Error(
3010
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
3011
+ );
3012
+ }
3013
+ function composeAnalysisWithOverlays(analysis, overlays) {
3014
+ const overlayIR = canonicalizeChainDSL(overlays);
3015
+ const overlayFields = collectOverlayFields(overlayIR.elements);
3016
+ if (overlayFields.length === 0) {
3017
+ return analysis;
3018
+ }
3019
+ const overlayByName = /* @__PURE__ */ new Map();
3020
+ for (const field of overlayFields) {
3021
+ if (overlayByName.has(field.name)) {
3022
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
3023
+ }
3024
+ overlayByName.set(field.name, field);
3025
+ }
3026
+ const mergedFields = [];
3027
+ for (const baseField of analysis.fields) {
3028
+ const overlayField = overlayByName.get(baseField.name);
3029
+ if (overlayField === void 0) {
3030
+ mergedFields.push(baseField);
3031
+ continue;
3032
+ }
3033
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
3034
+ overlayByName.delete(baseField.name);
3035
+ }
3036
+ if (overlayByName.size > 0) {
3037
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
3038
+ throw new Error(
3039
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
3040
+ );
3041
+ }
3042
+ return {
3043
+ ...analysis,
3044
+ fields: mergedFields
3045
+ };
3046
+ }
3047
+ function collectOverlayFields(elements) {
3048
+ const fields = [];
3049
+ for (const element of elements) {
3050
+ switch (element.kind) {
3051
+ case "field":
3052
+ fields.push(element);
3053
+ break;
3054
+ case "group":
3055
+ fields.push(...collectOverlayFields(element.elements));
3056
+ break;
3057
+ case "conditional":
3058
+ fields.push(...collectOverlayFields(element.elements));
3059
+ break;
3060
+ default: {
3061
+ const _exhaustive = element;
3062
+ void _exhaustive;
3063
+ }
3064
+ }
3065
+ }
3066
+ return fields;
3067
+ }
3068
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
3069
+ assertSupportedOverlayField(baseField, overlayField);
3070
+ return {
3071
+ ...baseField,
3072
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
3073
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
3074
+ };
3075
+ }
3076
+ function assertSupportedOverlayField(baseField, overlayField) {
3077
+ if (overlayField.constraints.length > 0) {
3078
+ throw new Error(
3079
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
3080
+ );
3081
+ }
3082
+ if (overlayField.required && !baseField.required) {
3083
+ throw new Error(
3084
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
3085
+ );
3086
+ }
3087
+ }
3088
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
3089
+ const { type: baseType } = baseField;
3090
+ const { type: overlayType } = overlayField;
3091
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
3092
+ throw new Error(
3093
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
3094
+ );
3095
+ }
3096
+ if (overlayType.kind === "dynamic") {
3097
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
3098
+ throw new Error(
3099
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
3100
+ );
3101
+ }
3102
+ return overlayType;
3103
+ }
3104
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
3105
+ throw new Error(
3106
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
3107
+ );
3108
+ }
3109
+ return baseType;
3110
+ }
3111
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
3112
+ const overlayType = overlayField.type;
3113
+ if (overlayType.kind !== "dynamic") {
3114
+ return false;
3115
+ }
3116
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
3117
+ if (resolvedBaseType === null) {
3118
+ return false;
3119
+ }
3120
+ if (overlayType.dynamicKind === "enum") {
3121
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
3122
+ }
3123
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
3124
+ }
3125
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
3126
+ if (type.kind !== "reference") {
3127
+ return type;
3128
+ }
3129
+ if (seen.has(type.name)) {
3130
+ return null;
3131
+ }
3132
+ const definition = typeRegistry[type.name];
3133
+ if (definition === void 0) {
3134
+ return null;
3135
+ }
3136
+ seen.add(type.name);
3137
+ return resolveReferenceType(definition.type, typeRegistry, seen);
3138
+ }
3139
+ function isSameStaticTypeShape(baseType, overlayType) {
3140
+ if (baseType.kind !== overlayType.kind) {
3141
+ return false;
3142
+ }
3143
+ switch (baseType.kind) {
3144
+ case "primitive":
3145
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
3146
+ case "enum":
3147
+ return overlayType.kind === "enum";
3148
+ case "dynamic":
3149
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
3150
+ case "record":
3151
+ return overlayType.kind === "record";
3152
+ case "reference":
3153
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
3154
+ case "union":
3155
+ return overlayType.kind === "union";
3156
+ case "custom":
3157
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
3158
+ case "object":
3159
+ case "array":
3160
+ return true;
3161
+ default: {
3162
+ const _exhaustive = baseType;
3163
+ return _exhaustive;
3164
+ }
3165
+ }
3166
+ }
3167
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
3168
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
3169
+ const overlayOnly = overlayAnnotations.filter(
3170
+ (annotation) => !baseKeys.has(annotationKey(annotation))
3171
+ );
3172
+ return [...baseAnnotations, ...overlayOnly];
3173
+ }
3174
+ function annotationKey(annotation) {
3175
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
3176
+ }
3177
+ var init_mixed_authoring = __esm({
3178
+ "src/generators/mixed-authoring.ts"() {
3179
+ "use strict";
3180
+ init_ir_generator();
3181
+ init_ir_generator2();
3182
+ init_canonicalize();
3183
+ init_program();
3184
+ init_class_analyzer();
3185
+ }
3186
+ });
3187
+
2181
3188
  // src/index.ts
2182
3189
  var index_exports = {};
2183
3190
  __export(index_exports, {
2184
3191
  buildFormSchemas: () => buildFormSchemas,
3192
+ buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
2185
3193
  categorizationSchema: () => categorizationSchema,
2186
3194
  categorySchema: () => categorySchema,
2187
3195
  controlSchema: () => controlSchema,
@@ -2247,6 +3255,7 @@ var init_index = __esm({
2247
3255
  init_ir_generator();
2248
3256
  init_generator2();
2249
3257
  init_class_schema();
3258
+ init_mixed_authoring();
2250
3259
  }
2251
3260
  });
2252
3261