@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/index.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  buildFormSchemas: () => buildFormSchemas,
34
+ buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
34
35
  categorizationSchema: () => categorizationSchema,
35
36
  categorySchema: () => categorySchema,
36
37
  controlSchema: () => controlSchema,
@@ -386,6 +387,7 @@ function canonicalizeTSDoc(analysis, source) {
386
387
  irVersion: import_core2.IR_VERSION,
387
388
  elements,
388
389
  typeRegistry: analysis.typeRegistry,
390
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
389
391
  provenance
390
392
  };
391
393
  }
@@ -462,6 +464,9 @@ function generateJsonSchemaFromIR(ir, options) {
462
464
  const ctx = makeContext(options);
463
465
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
464
466
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
467
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
468
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
469
+ }
465
470
  }
466
471
  const properties = {};
467
472
  const required = [];
@@ -473,6 +478,9 @@ function generateJsonSchemaFromIR(ir, options) {
473
478
  properties,
474
479
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
475
480
  };
481
+ if (ir.annotations && ir.annotations.length > 0) {
482
+ applyAnnotations(result, ir.annotations, ctx);
483
+ }
476
484
  if (Object.keys(ctx.defs).length > 0) {
477
485
  result.$defs = ctx.defs;
478
486
  }
@@ -502,22 +510,51 @@ function collectFields(elements, properties, required, ctx) {
502
510
  }
503
511
  function generateFieldSchema(field, ctx) {
504
512
  const schema = generateTypeNode(field.type, ctx);
513
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
505
514
  const directConstraints = [];
515
+ const itemConstraints = [];
506
516
  const pathConstraints = [];
507
517
  for (const c of field.constraints) {
508
518
  if (c.path) {
509
519
  pathConstraints.push(c);
520
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
521
+ itemConstraints.push(c);
510
522
  } else {
511
523
  directConstraints.push(c);
512
524
  }
513
525
  }
514
526
  applyConstraints(schema, directConstraints, ctx);
515
- applyAnnotations(schema, field.annotations, ctx);
527
+ if (itemStringSchema !== void 0) {
528
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
529
+ }
530
+ const rootAnnotations = [];
531
+ const itemAnnotations = [];
532
+ for (const annotation of field.annotations) {
533
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
534
+ itemAnnotations.push(annotation);
535
+ } else {
536
+ rootAnnotations.push(annotation);
537
+ }
538
+ }
539
+ applyAnnotations(schema, rootAnnotations, ctx);
540
+ if (itemStringSchema !== void 0) {
541
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
542
+ }
516
543
  if (pathConstraints.length === 0) {
517
544
  return schema;
518
545
  }
519
546
  return applyPathTargetedConstraints(schema, pathConstraints, ctx);
520
547
  }
548
+ function isStringItemConstraint(constraint) {
549
+ switch (constraint.constraintKind) {
550
+ case "minLength":
551
+ case "maxLength":
552
+ case "pattern":
553
+ return true;
554
+ default:
555
+ return false;
556
+ }
557
+ }
521
558
  function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
522
559
  if (schema.type === "array" && schema.items) {
523
560
  schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
@@ -735,6 +772,9 @@ function applyConstraints(schema, constraints, ctx) {
735
772
  case "uniqueItems":
736
773
  schema.uniqueItems = constraint.value;
737
774
  break;
775
+ case "const":
776
+ schema.const = constraint.value;
777
+ break;
738
778
  case "allowedMembers":
739
779
  break;
740
780
  case "custom":
@@ -759,8 +799,14 @@ function applyAnnotations(schema, annotations, ctx) {
759
799
  case "defaultValue":
760
800
  schema.default = annotation.value;
761
801
  break;
802
+ case "format":
803
+ schema.format = annotation.value;
804
+ break;
762
805
  case "deprecated":
763
806
  schema.deprecated = true;
807
+ if (annotation.message !== void 0 && annotation.message !== "") {
808
+ schema["x-formspec-deprecation-description"] = annotation.message;
809
+ }
764
810
  break;
765
811
  case "placeholder":
766
812
  break;
@@ -792,7 +838,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
792
838
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
793
839
  );
794
840
  }
795
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
841
+ assignVendorPrefixedExtensionKeywords(
842
+ schema,
843
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
844
+ ctx.vendorPrefix,
845
+ `custom constraint "${constraint.constraintId}"`
846
+ );
796
847
  }
797
848
  function applyCustomAnnotation(schema, annotation, ctx) {
798
849
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -804,7 +855,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
804
855
  if (registration.toJsonSchema === void 0) {
805
856
  return;
806
857
  }
807
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
858
+ assignVendorPrefixedExtensionKeywords(
859
+ schema,
860
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
861
+ ctx.vendorPrefix,
862
+ `custom annotation "${annotation.annotationId}"`
863
+ );
864
+ }
865
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
866
+ for (const [key, value] of Object.entries(extensionSchema)) {
867
+ if (!key.startsWith(`${vendorPrefix}-`)) {
868
+ throw new Error(
869
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
870
+ );
871
+ }
872
+ schema[key] = value;
873
+ }
808
874
  }
809
875
 
810
876
  // src/json-schema/generator.ts
@@ -948,25 +1014,31 @@ function createShowRule(fieldName, value) {
948
1014
  }
949
1015
  };
950
1016
  }
1017
+ function flattenConditionSchema(scope, schema) {
1018
+ if (schema.allOf === void 0) {
1019
+ if (scope === "#") {
1020
+ return [schema];
1021
+ }
1022
+ const fieldName = scope.replace("#/properties/", "");
1023
+ return [
1024
+ {
1025
+ properties: {
1026
+ [fieldName]: schema
1027
+ }
1028
+ }
1029
+ ];
1030
+ }
1031
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
1032
+ }
951
1033
  function combineRules(parentRule, childRule) {
952
- const parentCondition = parentRule.condition;
953
- const childCondition = childRule.condition;
954
1034
  return {
955
1035
  effect: "SHOW",
956
1036
  condition: {
957
1037
  scope: "#",
958
1038
  schema: {
959
1039
  allOf: [
960
- {
961
- properties: {
962
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
963
- }
964
- },
965
- {
966
- properties: {
967
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
968
- }
969
- }
1040
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
1041
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
970
1042
  ]
971
1043
  }
972
1044
  }
@@ -974,10 +1046,14 @@ function combineRules(parentRule, childRule) {
974
1046
  }
975
1047
  function fieldNodeToControl(field, parentRule) {
976
1048
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1049
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
977
1050
  const control = {
978
1051
  type: "Control",
979
1052
  scope: fieldToScope(field.name),
980
1053
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1054
+ ...placeholderAnnotation !== void 0 && {
1055
+ options: { placeholder: placeholderAnnotation.value }
1056
+ },
981
1057
  ...parentRule !== void 0 && { rule: parentRule }
982
1058
  };
983
1059
  return control;
@@ -1047,7 +1123,10 @@ function getSchemaExtension(schema, key) {
1047
1123
  // src/extensions/registry.ts
1048
1124
  function createExtensionRegistry(extensions) {
1049
1125
  const typeMap = /* @__PURE__ */ new Map();
1126
+ const typeNameMap = /* @__PURE__ */ new Map();
1050
1127
  const constraintMap = /* @__PURE__ */ new Map();
1128
+ const constraintTagMap = /* @__PURE__ */ new Map();
1129
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
1051
1130
  const annotationMap = /* @__PURE__ */ new Map();
1052
1131
  for (const ext of extensions) {
1053
1132
  if (ext.types !== void 0) {
@@ -1057,6 +1136,27 @@ function createExtensionRegistry(extensions) {
1057
1136
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1058
1137
  }
1059
1138
  typeMap.set(qualifiedId, type);
1139
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
1140
+ if (typeNameMap.has(sourceTypeName)) {
1141
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
1142
+ }
1143
+ typeNameMap.set(sourceTypeName, {
1144
+ extensionId: ext.extensionId,
1145
+ registration: type
1146
+ });
1147
+ }
1148
+ if (type.builtinConstraintBroadenings !== void 0) {
1149
+ for (const broadening of type.builtinConstraintBroadenings) {
1150
+ const key = `${qualifiedId}:${broadening.tagName}`;
1151
+ if (builtinBroadeningMap.has(key)) {
1152
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
1153
+ }
1154
+ builtinBroadeningMap.set(key, {
1155
+ extensionId: ext.extensionId,
1156
+ registration: broadening
1157
+ });
1158
+ }
1159
+ }
1060
1160
  }
1061
1161
  }
1062
1162
  if (ext.constraints !== void 0) {
@@ -1068,6 +1168,17 @@ function createExtensionRegistry(extensions) {
1068
1168
  constraintMap.set(qualifiedId, constraint);
1069
1169
  }
1070
1170
  }
1171
+ if (ext.constraintTags !== void 0) {
1172
+ for (const tag of ext.constraintTags) {
1173
+ if (constraintTagMap.has(tag.tagName)) {
1174
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
1175
+ }
1176
+ constraintTagMap.set(tag.tagName, {
1177
+ extensionId: ext.extensionId,
1178
+ registration: tag
1179
+ });
1180
+ }
1181
+ }
1071
1182
  if (ext.annotations !== void 0) {
1072
1183
  for (const annotation of ext.annotations) {
1073
1184
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -1081,7 +1192,10 @@ function createExtensionRegistry(extensions) {
1081
1192
  return {
1082
1193
  extensions,
1083
1194
  findType: (typeId) => typeMap.get(typeId),
1195
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
1084
1196
  findConstraint: (constraintId) => constraintMap.get(constraintId),
1197
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
1198
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
1085
1199
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
1086
1200
  };
1087
1201
  }
@@ -1254,8 +1368,8 @@ var LENGTH_CONSTRAINT_MAP = {
1254
1368
  minItems: "minItems",
1255
1369
  maxItems: "maxItems"
1256
1370
  };
1257
- var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1258
- function createFormSpecTSDocConfig() {
1371
+ var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1372
+ function createFormSpecTSDocConfig(extensionTagNames = []) {
1259
1373
  const config = new import_tsdoc.TSDocConfiguration();
1260
1374
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
1261
1375
  config.addTagDefinition(
@@ -1266,7 +1380,16 @@ function createFormSpecTSDocConfig() {
1266
1380
  })
1267
1381
  );
1268
1382
  }
1269
- for (const tagName of ["displayName", "description"]) {
1383
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1384
+ config.addTagDefinition(
1385
+ new import_tsdoc.TSDocTagDefinition({
1386
+ tagName: "@" + tagName,
1387
+ syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
1388
+ allowMultiple: true
1389
+ })
1390
+ );
1391
+ }
1392
+ for (const tagName of extensionTagNames) {
1270
1393
  config.addTagDefinition(
1271
1394
  new import_tsdoc.TSDocTagDefinition({
1272
1395
  tagName: "@" + tagName,
@@ -1277,14 +1400,31 @@ function createFormSpecTSDocConfig() {
1277
1400
  }
1278
1401
  return config;
1279
1402
  }
1280
- var sharedParser;
1281
- function getParser() {
1282
- sharedParser ??= new import_tsdoc.TSDocParser(createFormSpecTSDocConfig());
1283
- return sharedParser;
1284
- }
1285
- function parseTSDocTags(node, file = "") {
1403
+ var parserCache = /* @__PURE__ */ new Map();
1404
+ function getParser(options) {
1405
+ const extensionTagNames = [
1406
+ ...options?.extensionRegistry?.extensions.flatMap(
1407
+ (extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
1408
+ ) ?? []
1409
+ ].sort();
1410
+ const cacheKey = extensionTagNames.join("|");
1411
+ const existing = parserCache.get(cacheKey);
1412
+ if (existing) {
1413
+ return existing;
1414
+ }
1415
+ const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
1416
+ parserCache.set(cacheKey, parser);
1417
+ return parser;
1418
+ }
1419
+ function parseTSDocTags(node, file = "", options) {
1286
1420
  const constraints = [];
1287
1421
  const annotations = [];
1422
+ let displayName;
1423
+ let description;
1424
+ let placeholder;
1425
+ let displayNameProvenance;
1426
+ let descriptionProvenance;
1427
+ let placeholderProvenance;
1288
1428
  const sourceFile = node.getSourceFile();
1289
1429
  const sourceText = sourceFile.getFullText();
1290
1430
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1297,52 +1437,92 @@ function parseTSDocTags(node, file = "") {
1297
1437
  if (!commentText.startsWith("/**")) {
1298
1438
  continue;
1299
1439
  }
1300
- const parser = getParser();
1440
+ const parser = getParser(options);
1301
1441
  const parserContext = parser.parseRange(
1302
1442
  import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
1303
1443
  );
1304
1444
  const docComment = parserContext.docComment;
1305
1445
  for (const block of docComment.customBlocks) {
1306
1446
  const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
1307
- if (tagName === "displayName" || tagName === "description") {
1447
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1308
1448
  const text2 = extractBlockText(block).trim();
1309
1449
  if (text2 === "") continue;
1310
1450
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1311
1451
  if (tagName === "displayName") {
1452
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1453
+ displayName = text2;
1454
+ displayNameProvenance = provenance2;
1455
+ }
1456
+ } else if (tagName === "format") {
1312
1457
  annotations.push({
1313
1458
  kind: "annotation",
1314
- annotationKind: "displayName",
1459
+ annotationKind: "format",
1315
1460
  value: text2,
1316
1461
  provenance: provenance2
1317
1462
  });
1318
1463
  } else {
1319
- annotations.push({
1320
- kind: "annotation",
1321
- annotationKind: "description",
1322
- value: text2,
1323
- provenance: provenance2
1324
- });
1464
+ if (tagName === "description" && description === void 0) {
1465
+ description = text2;
1466
+ descriptionProvenance = provenance2;
1467
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1468
+ placeholder = text2;
1469
+ placeholderProvenance = provenance2;
1470
+ }
1325
1471
  }
1326
1472
  continue;
1327
1473
  }
1328
1474
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1329
1475
  const text = extractBlockText(block).trim();
1330
- if (text === "") continue;
1476
+ const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1477
+ if (text === "" && expectedType !== "boolean") continue;
1331
1478
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1332
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1479
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1333
1480
  if (constraintNode) {
1334
1481
  constraints.push(constraintNode);
1335
1482
  }
1336
1483
  }
1337
1484
  if (docComment.deprecatedBlock !== void 0) {
1485
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1338
1486
  annotations.push({
1339
1487
  kind: "annotation",
1340
1488
  annotationKind: "deprecated",
1489
+ ...message !== "" && { message },
1341
1490
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1342
1491
  });
1343
1492
  }
1493
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1494
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1495
+ if (remarks !== "") {
1496
+ description = remarks;
1497
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1498
+ }
1499
+ }
1344
1500
  }
1345
1501
  }
1502
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1503
+ annotations.push({
1504
+ kind: "annotation",
1505
+ annotationKind: "displayName",
1506
+ value: displayName,
1507
+ provenance: displayNameProvenance
1508
+ });
1509
+ }
1510
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1511
+ annotations.push({
1512
+ kind: "annotation",
1513
+ annotationKind: "description",
1514
+ value: description,
1515
+ provenance: descriptionProvenance
1516
+ });
1517
+ }
1518
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1519
+ annotations.push({
1520
+ kind: "annotation",
1521
+ annotationKind: "placeholder",
1522
+ value: placeholder,
1523
+ provenance: placeholderProvenance
1524
+ });
1525
+ }
1346
1526
  const jsDocTagsAll = ts2.getJSDocTags(node);
1347
1527
  for (const tag of jsDocTagsAll) {
1348
1528
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
@@ -1351,13 +1531,40 @@ function parseTSDocTags(node, file = "") {
1351
1531
  if (commentText === void 0 || commentText.trim() === "") continue;
1352
1532
  const text = commentText.trim();
1353
1533
  const provenance = provenanceForJSDocTag(tag, file);
1354
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1534
+ if (tagName === "defaultValue") {
1535
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1536
+ annotations.push(defaultValueNode);
1537
+ continue;
1538
+ }
1539
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1355
1540
  if (constraintNode) {
1356
1541
  constraints.push(constraintNode);
1357
1542
  }
1358
1543
  }
1359
1544
  return { constraints, annotations };
1360
1545
  }
1546
+ function extractDisplayNameMetadata(node) {
1547
+ let displayName;
1548
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1549
+ for (const tag of ts2.getJSDocTags(node)) {
1550
+ const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1551
+ if (tagName !== "displayName") continue;
1552
+ const commentText = getTagCommentText(tag);
1553
+ if (commentText === void 0) continue;
1554
+ const text = commentText.trim();
1555
+ if (text === "") continue;
1556
+ const memberTarget = parseMemberTargetDisplayName(text);
1557
+ if (memberTarget) {
1558
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1559
+ continue;
1560
+ }
1561
+ displayName ??= text;
1562
+ }
1563
+ return {
1564
+ ...displayName !== void 0 && { displayName },
1565
+ memberDisplayNames
1566
+ };
1567
+ }
1361
1568
  function extractPathTarget(text) {
1362
1569
  const trimmed = text.trimStart();
1363
1570
  const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
@@ -1385,7 +1592,11 @@ function extractPlainText(node) {
1385
1592
  }
1386
1593
  return result;
1387
1594
  }
1388
- function parseConstraintValue(tagName, text, provenance) {
1595
+ function parseConstraintValue(tagName, text, provenance, options) {
1596
+ const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
1597
+ if (customConstraint) {
1598
+ return customConstraint;
1599
+ }
1389
1600
  if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1390
1601
  return null;
1391
1602
  }
@@ -1420,7 +1631,45 @@ function parseConstraintValue(tagName, text, provenance) {
1420
1631
  }
1421
1632
  return null;
1422
1633
  }
1634
+ if (expectedType === "boolean") {
1635
+ const trimmed = effectiveText.trim();
1636
+ if (trimmed !== "" && trimmed !== "true") {
1637
+ return null;
1638
+ }
1639
+ if (tagName === "uniqueItems") {
1640
+ return {
1641
+ kind: "constraint",
1642
+ constraintKind: "uniqueItems",
1643
+ value: true,
1644
+ ...path3 && { path: path3 },
1645
+ provenance
1646
+ };
1647
+ }
1648
+ return null;
1649
+ }
1423
1650
  if (expectedType === "json") {
1651
+ if (tagName === "const") {
1652
+ const trimmedText = effectiveText.trim();
1653
+ if (trimmedText === "") return null;
1654
+ try {
1655
+ const parsed2 = JSON.parse(trimmedText);
1656
+ return {
1657
+ kind: "constraint",
1658
+ constraintKind: "const",
1659
+ value: parsed2,
1660
+ ...path3 && { path: path3 },
1661
+ provenance
1662
+ };
1663
+ } catch {
1664
+ return {
1665
+ kind: "constraint",
1666
+ constraintKind: "const",
1667
+ value: trimmedText,
1668
+ ...path3 && { path: path3 },
1669
+ provenance
1670
+ };
1671
+ }
1672
+ }
1424
1673
  const parsed = tryParseJson(effectiveText);
1425
1674
  if (!Array.isArray(parsed)) {
1426
1675
  return null;
@@ -1452,6 +1701,111 @@ function parseConstraintValue(tagName, text, provenance) {
1452
1701
  provenance
1453
1702
  };
1454
1703
  }
1704
+ function parseExtensionConstraintValue(tagName, text, provenance, options) {
1705
+ const pathResult = extractPathTarget(text);
1706
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1707
+ const path3 = pathResult?.path;
1708
+ const registry = options?.extensionRegistry;
1709
+ if (registry === void 0) {
1710
+ return null;
1711
+ }
1712
+ const directTag = registry.findConstraintTag(tagName);
1713
+ if (directTag !== void 0) {
1714
+ return makeCustomConstraintNode(
1715
+ directTag.extensionId,
1716
+ directTag.registration.constraintName,
1717
+ directTag.registration.parseValue(effectiveText),
1718
+ provenance,
1719
+ path3,
1720
+ registry
1721
+ );
1722
+ }
1723
+ if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1724
+ return null;
1725
+ }
1726
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
1727
+ if (broadenedTypeId === void 0) {
1728
+ return null;
1729
+ }
1730
+ const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
1731
+ if (broadened === void 0) {
1732
+ return null;
1733
+ }
1734
+ return makeCustomConstraintNode(
1735
+ broadened.extensionId,
1736
+ broadened.registration.constraintName,
1737
+ broadened.registration.parseValue(effectiveText),
1738
+ provenance,
1739
+ path3,
1740
+ registry
1741
+ );
1742
+ }
1743
+ function getBroadenedCustomTypeId(fieldType) {
1744
+ if (fieldType?.kind === "custom") {
1745
+ return fieldType.typeId;
1746
+ }
1747
+ if (fieldType?.kind !== "union") {
1748
+ return void 0;
1749
+ }
1750
+ const customMembers = fieldType.members.filter(
1751
+ (member) => member.kind === "custom"
1752
+ );
1753
+ if (customMembers.length !== 1) {
1754
+ return void 0;
1755
+ }
1756
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
1757
+ const allOtherMembersAreNull = nonCustomMembers.every(
1758
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
1759
+ );
1760
+ const customMember = customMembers[0];
1761
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
1762
+ }
1763
+ function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path3, registry) {
1764
+ const constraintId = `${extensionId}/${constraintName}`;
1765
+ const registration = registry.findConstraint(constraintId);
1766
+ if (registration === void 0) {
1767
+ throw new Error(
1768
+ `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
1769
+ );
1770
+ }
1771
+ return {
1772
+ kind: "constraint",
1773
+ constraintKind: "custom",
1774
+ constraintId,
1775
+ payload,
1776
+ compositionRule: registration.compositionRule,
1777
+ ...path3 && { path: path3 },
1778
+ provenance
1779
+ };
1780
+ }
1781
+ function parseDefaultValueValue(text, provenance) {
1782
+ const trimmed = text.trim();
1783
+ let value;
1784
+ if (trimmed === "null") {
1785
+ value = null;
1786
+ } else if (trimmed === "true") {
1787
+ value = true;
1788
+ } else if (trimmed === "false") {
1789
+ value = false;
1790
+ } else {
1791
+ const parsed = tryParseJson(trimmed);
1792
+ value = parsed !== null ? parsed : trimmed;
1793
+ }
1794
+ return {
1795
+ kind: "annotation",
1796
+ annotationKind: "defaultValue",
1797
+ value,
1798
+ provenance
1799
+ };
1800
+ }
1801
+ function isMemberTargetDisplayName(text) {
1802
+ return parseMemberTargetDisplayName(text) !== null;
1803
+ }
1804
+ function parseMemberTargetDisplayName(text) {
1805
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1806
+ if (!match?.[1] || !match[2]) return null;
1807
+ return { target: match[1], label: match[2].trim() };
1808
+ }
1455
1809
  function provenanceForComment(range, sourceFile, file, tagName) {
1456
1810
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1457
1811
  return {
@@ -1484,12 +1838,12 @@ function getTagCommentText(tag) {
1484
1838
  }
1485
1839
 
1486
1840
  // src/analyzer/jsdoc-constraints.ts
1487
- function extractJSDocConstraintNodes(node, file = "") {
1488
- const result = parseTSDocTags(node, file);
1841
+ function extractJSDocConstraintNodes(node, file = "", options) {
1842
+ const result = parseTSDocTags(node, file, options);
1489
1843
  return [...result.constraints];
1490
1844
  }
1491
- function extractJSDocAnnotationNodes(node, file = "") {
1492
- const result = parseTSDocTags(node, file);
1845
+ function extractJSDocAnnotationNodes(node, file = "", options) {
1846
+ const result = parseTSDocTags(node, file, options);
1493
1847
  return [...result.annotations];
1494
1848
  }
1495
1849
  function extractDefaultValueAnnotation(initializer, file = "") {
@@ -1533,17 +1887,43 @@ function isObjectType(type) {
1533
1887
  function isTypeReference(type) {
1534
1888
  return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1535
1889
  }
1536
- function analyzeClassToIR(classDecl, checker, file = "") {
1890
+ var RESOLVING_TYPE_PLACEHOLDER = {
1891
+ kind: "object",
1892
+ properties: [],
1893
+ additionalProperties: true
1894
+ };
1895
+ function makeParseOptions(extensionRegistry, fieldType) {
1896
+ if (extensionRegistry === void 0 && fieldType === void 0) {
1897
+ return void 0;
1898
+ }
1899
+ return {
1900
+ ...extensionRegistry !== void 0 && { extensionRegistry },
1901
+ ...fieldType !== void 0 && { fieldType }
1902
+ };
1903
+ }
1904
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1537
1905
  const name = classDecl.name?.text ?? "AnonymousClass";
1538
1906
  const fields = [];
1539
1907
  const fieldLayouts = [];
1540
1908
  const typeRegistry = {};
1909
+ const annotations = extractJSDocAnnotationNodes(
1910
+ classDecl,
1911
+ file,
1912
+ makeParseOptions(extensionRegistry)
1913
+ );
1541
1914
  const visiting = /* @__PURE__ */ new Set();
1542
1915
  const instanceMethods = [];
1543
1916
  const staticMethods = [];
1544
1917
  for (const member of classDecl.members) {
1545
1918
  if (ts4.isPropertyDeclaration(member)) {
1546
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1919
+ const fieldNode = analyzeFieldToIR(
1920
+ member,
1921
+ checker,
1922
+ file,
1923
+ typeRegistry,
1924
+ visiting,
1925
+ extensionRegistry
1926
+ );
1547
1927
  if (fieldNode) {
1548
1928
  fields.push(fieldNode);
1549
1929
  fieldLayouts.push({});
@@ -1560,25 +1940,53 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1560
1940
  }
1561
1941
  }
1562
1942
  }
1563
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1943
+ return {
1944
+ name,
1945
+ fields,
1946
+ fieldLayouts,
1947
+ typeRegistry,
1948
+ ...annotations.length > 0 && { annotations },
1949
+ instanceMethods,
1950
+ staticMethods
1951
+ };
1564
1952
  }
1565
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1953
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
1566
1954
  const name = interfaceDecl.name.text;
1567
1955
  const fields = [];
1568
1956
  const typeRegistry = {};
1957
+ const annotations = extractJSDocAnnotationNodes(
1958
+ interfaceDecl,
1959
+ file,
1960
+ makeParseOptions(extensionRegistry)
1961
+ );
1569
1962
  const visiting = /* @__PURE__ */ new Set();
1570
1963
  for (const member of interfaceDecl.members) {
1571
1964
  if (ts4.isPropertySignature(member)) {
1572
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1965
+ const fieldNode = analyzeInterfacePropertyToIR(
1966
+ member,
1967
+ checker,
1968
+ file,
1969
+ typeRegistry,
1970
+ visiting,
1971
+ extensionRegistry
1972
+ );
1573
1973
  if (fieldNode) {
1574
1974
  fields.push(fieldNode);
1575
1975
  }
1576
1976
  }
1577
1977
  }
1578
1978
  const fieldLayouts = fields.map(() => ({}));
1579
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1979
+ return {
1980
+ name,
1981
+ fields,
1982
+ fieldLayouts,
1983
+ typeRegistry,
1984
+ ...annotations.length > 0 && { annotations },
1985
+ instanceMethods: [],
1986
+ staticMethods: []
1987
+ };
1580
1988
  }
1581
- function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1989
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1582
1990
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1583
1991
  const sourceFile = typeAlias.getSourceFile();
1584
1992
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -1591,10 +1999,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1591
1999
  const name = typeAlias.name.text;
1592
2000
  const fields = [];
1593
2001
  const typeRegistry = {};
2002
+ const annotations = extractJSDocAnnotationNodes(
2003
+ typeAlias,
2004
+ file,
2005
+ makeParseOptions(extensionRegistry)
2006
+ );
1594
2007
  const visiting = /* @__PURE__ */ new Set();
1595
2008
  for (const member of typeAlias.type.members) {
1596
2009
  if (ts4.isPropertySignature(member)) {
1597
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2010
+ const fieldNode = analyzeInterfacePropertyToIR(
2011
+ member,
2012
+ checker,
2013
+ file,
2014
+ typeRegistry,
2015
+ visiting,
2016
+ extensionRegistry
2017
+ );
1598
2018
  if (fieldNode) {
1599
2019
  fields.push(fieldNode);
1600
2020
  }
@@ -1607,12 +2027,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1607
2027
  fields,
1608
2028
  fieldLayouts: fields.map(() => ({})),
1609
2029
  typeRegistry,
2030
+ ...annotations.length > 0 && { annotations },
1610
2031
  instanceMethods: [],
1611
2032
  staticMethods: []
1612
2033
  }
1613
2034
  };
1614
2035
  }
1615
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
2036
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1616
2037
  if (!ts4.isIdentifier(prop.name)) {
1617
2038
  return null;
1618
2039
  }
@@ -1620,16 +2041,28 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1620
2041
  const tsType = checker.getTypeAtLocation(prop);
1621
2042
  const optional = prop.questionToken !== void 0;
1622
2043
  const provenance = provenanceForNode(prop, file);
1623
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
2044
+ let type = resolveTypeNode(
2045
+ tsType,
2046
+ checker,
2047
+ file,
2048
+ typeRegistry,
2049
+ visiting,
2050
+ prop,
2051
+ extensionRegistry
2052
+ );
1624
2053
  const constraints = [];
1625
2054
  if (prop.type) {
1626
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2055
+ constraints.push(
2056
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2057
+ );
1627
2058
  }
1628
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2059
+ constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
1629
2060
  let annotations = [];
1630
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2061
+ annotations.push(
2062
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2063
+ );
1631
2064
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1632
- if (defaultAnnotation) {
2065
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1633
2066
  annotations.push(defaultAnnotation);
1634
2067
  }
1635
2068
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
@@ -1643,7 +2076,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1643
2076
  provenance
1644
2077
  };
1645
2078
  }
1646
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
2079
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1647
2080
  if (!ts4.isIdentifier(prop.name)) {
1648
2081
  return null;
1649
2082
  }
@@ -1651,14 +2084,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1651
2084
  const tsType = checker.getTypeAtLocation(prop);
1652
2085
  const optional = prop.questionToken !== void 0;
1653
2086
  const provenance = provenanceForNode(prop, file);
1654
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
2087
+ let type = resolveTypeNode(
2088
+ tsType,
2089
+ checker,
2090
+ file,
2091
+ typeRegistry,
2092
+ visiting,
2093
+ prop,
2094
+ extensionRegistry
2095
+ );
1655
2096
  const constraints = [];
1656
2097
  if (prop.type) {
1657
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2098
+ constraints.push(
2099
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2100
+ );
1658
2101
  }
1659
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2102
+ constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
1660
2103
  let annotations = [];
1661
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2104
+ annotations.push(
2105
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2106
+ );
1662
2107
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1663
2108
  return {
1664
2109
  kind: "field",
@@ -1732,7 +2177,66 @@ function parseEnumMemberDisplayName(value) {
1732
2177
  if (label === "") return null;
1733
2178
  return { value: match[1], label };
1734
2179
  }
1735
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
2180
+ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
2181
+ if (sourceNode === void 0 || extensionRegistry === void 0) {
2182
+ return null;
2183
+ }
2184
+ const typeNode = extractTypeNodeFromSource(sourceNode);
2185
+ if (typeNode === void 0) {
2186
+ return null;
2187
+ }
2188
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
2189
+ }
2190
+ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
2191
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
2192
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
2193
+ }
2194
+ const typeName = getTypeNodeRegistrationName(typeNode);
2195
+ if (typeName === null) {
2196
+ return null;
2197
+ }
2198
+ const registration = extensionRegistry.findTypeByName(typeName);
2199
+ if (registration !== void 0) {
2200
+ return {
2201
+ kind: "custom",
2202
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
2203
+ payload: null
2204
+ };
2205
+ }
2206
+ if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
2207
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
2208
+ if (aliasDecl !== void 0) {
2209
+ return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
2210
+ }
2211
+ }
2212
+ return null;
2213
+ }
2214
+ function extractTypeNodeFromSource(sourceNode) {
2215
+ if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
2216
+ return sourceNode.type;
2217
+ }
2218
+ if (ts4.isTypeNode(sourceNode)) {
2219
+ return sourceNode;
2220
+ }
2221
+ return void 0;
2222
+ }
2223
+ function getTypeNodeRegistrationName(typeNode) {
2224
+ if (ts4.isTypeReferenceNode(typeNode)) {
2225
+ return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
2226
+ }
2227
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
2228
+ return getTypeNodeRegistrationName(typeNode.type);
2229
+ }
2230
+ if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
2231
+ return typeNode.getText();
2232
+ }
2233
+ return null;
2234
+ }
2235
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2236
+ const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2237
+ if (customType) {
2238
+ return customType;
2239
+ }
1736
2240
  if (type.flags & ts4.TypeFlags.String) {
1737
2241
  return { kind: "primitive", primitiveKind: "string" };
1738
2242
  }
@@ -1761,88 +2265,162 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1761
2265
  };
1762
2266
  }
1763
2267
  if (type.isUnion()) {
1764
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
2268
+ return resolveUnionType(
2269
+ type,
2270
+ checker,
2271
+ file,
2272
+ typeRegistry,
2273
+ visiting,
2274
+ sourceNode,
2275
+ extensionRegistry
2276
+ );
1765
2277
  }
1766
2278
  if (checker.isArrayType(type)) {
1767
- return resolveArrayType(type, checker, file, typeRegistry, visiting);
2279
+ return resolveArrayType(
2280
+ type,
2281
+ checker,
2282
+ file,
2283
+ typeRegistry,
2284
+ visiting,
2285
+ sourceNode,
2286
+ extensionRegistry
2287
+ );
1768
2288
  }
1769
2289
  if (isObjectType(type)) {
1770
- return resolveObjectType(type, checker, file, typeRegistry, visiting);
2290
+ return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
1771
2291
  }
1772
2292
  return { kind: "primitive", primitiveKind: "string" };
1773
2293
  }
1774
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
2294
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2295
+ const typeName = getNamedTypeName(type);
2296
+ const namedDecl = getNamedTypeDeclaration(type);
2297
+ if (typeName && typeName in typeRegistry) {
2298
+ return { kind: "reference", name: typeName, typeArguments: [] };
2299
+ }
1775
2300
  const allTypes = type.types;
2301
+ const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
2302
+ const nonNullSourceNodes = unionMemberTypeNodes.filter(
2303
+ (memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
2304
+ );
1776
2305
  const nonNullTypes = allTypes.filter(
1777
- (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
2306
+ (memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1778
2307
  );
2308
+ const nonNullMembers = nonNullTypes.map((memberType, index) => ({
2309
+ memberType,
2310
+ sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
2311
+ }));
1779
2312
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2313
+ const memberDisplayNames = /* @__PURE__ */ new Map();
2314
+ if (namedDecl) {
2315
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
2316
+ memberDisplayNames.set(value, label);
2317
+ }
2318
+ }
2319
+ if (sourceNode) {
2320
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
2321
+ memberDisplayNames.set(value, label);
2322
+ }
2323
+ }
2324
+ const registerNamed = (result) => {
2325
+ if (!typeName) {
2326
+ return result;
2327
+ }
2328
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2329
+ typeRegistry[typeName] = {
2330
+ name: typeName,
2331
+ type: result,
2332
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2333
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
2334
+ };
2335
+ return { kind: "reference", name: typeName, typeArguments: [] };
2336
+ };
2337
+ const applyMemberLabels = (members2) => members2.map((value) => {
2338
+ const displayName = memberDisplayNames.get(String(value));
2339
+ return displayName !== void 0 ? { value, displayName } : { value };
2340
+ });
1780
2341
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1781
2342
  if (isBooleanUnion2) {
1782
2343
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1783
- if (hasNull) {
1784
- return {
1785
- kind: "union",
1786
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1787
- };
1788
- }
1789
- return boolNode;
2344
+ const result = hasNull ? {
2345
+ kind: "union",
2346
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
2347
+ } : boolNode;
2348
+ return registerNamed(result);
1790
2349
  }
1791
2350
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1792
2351
  if (allStringLiterals && nonNullTypes.length > 0) {
1793
2352
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1794
2353
  const enumNode = {
1795
2354
  kind: "enum",
1796
- members: stringTypes.map((t) => ({ value: t.value }))
2355
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1797
2356
  };
1798
- if (hasNull) {
1799
- return {
1800
- kind: "union",
1801
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1802
- };
1803
- }
1804
- return enumNode;
2357
+ const result = hasNull ? {
2358
+ kind: "union",
2359
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2360
+ } : enumNode;
2361
+ return registerNamed(result);
1805
2362
  }
1806
2363
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1807
2364
  if (allNumberLiterals && nonNullTypes.length > 0) {
1808
2365
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1809
2366
  const enumNode = {
1810
2367
  kind: "enum",
1811
- members: numberTypes.map((t) => ({ value: t.value }))
2368
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1812
2369
  };
1813
- if (hasNull) {
1814
- return {
1815
- kind: "union",
1816
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1817
- };
1818
- }
1819
- return enumNode;
1820
- }
1821
- if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1822
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1823
- if (hasNull) {
1824
- return {
1825
- kind: "union",
1826
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1827
- };
1828
- }
1829
- return inner;
1830
- }
1831
- const members = nonNullTypes.map(
1832
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2370
+ const result = hasNull ? {
2371
+ kind: "union",
2372
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2373
+ } : enumNode;
2374
+ return registerNamed(result);
2375
+ }
2376
+ if (nonNullMembers.length === 1 && nonNullMembers[0]) {
2377
+ const inner = resolveTypeNode(
2378
+ nonNullMembers[0].memberType,
2379
+ checker,
2380
+ file,
2381
+ typeRegistry,
2382
+ visiting,
2383
+ nonNullMembers[0].sourceNode ?? sourceNode,
2384
+ extensionRegistry
2385
+ );
2386
+ const result = hasNull ? {
2387
+ kind: "union",
2388
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2389
+ } : inner;
2390
+ return registerNamed(result);
2391
+ }
2392
+ const members = nonNullMembers.map(
2393
+ ({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
2394
+ memberType,
2395
+ checker,
2396
+ file,
2397
+ typeRegistry,
2398
+ visiting,
2399
+ memberSourceNode ?? sourceNode,
2400
+ extensionRegistry
2401
+ )
1833
2402
  );
1834
2403
  if (hasNull) {
1835
2404
  members.push({ kind: "primitive", primitiveKind: "null" });
1836
2405
  }
1837
- return { kind: "union", members };
2406
+ return registerNamed({ kind: "union", members });
1838
2407
  }
1839
- function resolveArrayType(type, checker, file, typeRegistry, visiting) {
2408
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1840
2409
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
1841
2410
  const elementType = typeArgs?.[0];
1842
- const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
2411
+ const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
2412
+ const items = elementType ? resolveTypeNode(
2413
+ elementType,
2414
+ checker,
2415
+ file,
2416
+ typeRegistry,
2417
+ visiting,
2418
+ elementSourceNode,
2419
+ extensionRegistry
2420
+ ) : { kind: "primitive", primitiveKind: "string" };
1843
2421
  return { kind: "array", items };
1844
2422
  }
1845
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2423
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1846
2424
  if (type.getProperties().length > 0) {
1847
2425
  return null;
1848
2426
  }
@@ -1850,39 +2428,123 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1850
2428
  if (!indexInfo) {
1851
2429
  return null;
1852
2430
  }
1853
- if (visiting.has(type)) {
1854
- return null;
1855
- }
1856
- visiting.add(type);
1857
- try {
1858
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1859
- return { kind: "record", valueType };
1860
- } finally {
1861
- visiting.delete(type);
1862
- }
2431
+ const valueType = resolveTypeNode(
2432
+ indexInfo.type,
2433
+ checker,
2434
+ file,
2435
+ typeRegistry,
2436
+ visiting,
2437
+ void 0,
2438
+ extensionRegistry
2439
+ );
2440
+ return { kind: "record", valueType };
1863
2441
  }
1864
- function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1865
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1866
- if (recordNode) {
1867
- return recordNode;
2442
+ function typeNodeContainsReference(type, targetName) {
2443
+ switch (type.kind) {
2444
+ case "reference":
2445
+ return type.name === targetName;
2446
+ case "array":
2447
+ return typeNodeContainsReference(type.items, targetName);
2448
+ case "record":
2449
+ return typeNodeContainsReference(type.valueType, targetName);
2450
+ case "union":
2451
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2452
+ case "object":
2453
+ return type.properties.some(
2454
+ (property) => typeNodeContainsReference(property.type, targetName)
2455
+ );
2456
+ case "primitive":
2457
+ case "enum":
2458
+ case "dynamic":
2459
+ case "custom":
2460
+ return false;
2461
+ default: {
2462
+ const _exhaustive = type;
2463
+ return _exhaustive;
2464
+ }
1868
2465
  }
2466
+ }
2467
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2468
+ const typeName = getNamedTypeName(type);
2469
+ const namedTypeName = typeName ?? void 0;
2470
+ const namedDecl = getNamedTypeDeclaration(type);
2471
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2472
+ const clearNamedTypeRegistration = () => {
2473
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2474
+ return;
2475
+ }
2476
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2477
+ };
1869
2478
  if (visiting.has(type)) {
2479
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2480
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2481
+ }
1870
2482
  return { kind: "object", properties: [], additionalProperties: false };
1871
2483
  }
2484
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2485
+ typeRegistry[namedTypeName] = {
2486
+ name: namedTypeName,
2487
+ type: RESOLVING_TYPE_PLACEHOLDER,
2488
+ provenance: provenanceForDeclaration(namedDecl, file)
2489
+ };
2490
+ }
1872
2491
  visiting.add(type);
1873
- const typeName = getNamedTypeName(type);
1874
- if (typeName && typeName in typeRegistry) {
2492
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2493
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2494
+ visiting.delete(type);
2495
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2496
+ }
2497
+ }
2498
+ const recordNode = tryResolveRecordType(
2499
+ type,
2500
+ checker,
2501
+ file,
2502
+ typeRegistry,
2503
+ visiting,
2504
+ extensionRegistry
2505
+ );
2506
+ if (recordNode) {
1875
2507
  visiting.delete(type);
1876
- return { kind: "reference", name: typeName, typeArguments: [] };
2508
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2509
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2510
+ if (!isRecursiveRecord) {
2511
+ clearNamedTypeRegistration();
2512
+ return recordNode;
2513
+ }
2514
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2515
+ typeRegistry[namedTypeName] = {
2516
+ name: namedTypeName,
2517
+ type: recordNode,
2518
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2519
+ provenance: provenanceForDeclaration(namedDecl, file)
2520
+ };
2521
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2522
+ }
2523
+ return recordNode;
1877
2524
  }
1878
2525
  const properties = [];
1879
- const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
2526
+ const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
2527
+ type,
2528
+ checker,
2529
+ file,
2530
+ typeRegistry,
2531
+ visiting,
2532
+ extensionRegistry
2533
+ );
1880
2534
  for (const prop of type.getProperties()) {
1881
2535
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1882
2536
  if (!declaration) continue;
1883
2537
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1884
2538
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1885
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2539
+ const propTypeNode = resolveTypeNode(
2540
+ propType,
2541
+ checker,
2542
+ file,
2543
+ typeRegistry,
2544
+ visiting,
2545
+ declaration,
2546
+ extensionRegistry
2547
+ );
1886
2548
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1887
2549
  properties.push({
1888
2550
  name: prop.name,
@@ -1899,17 +2561,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1899
2561
  properties,
1900
2562
  additionalProperties: true
1901
2563
  };
1902
- if (typeName) {
1903
- typeRegistry[typeName] = {
1904
- name: typeName,
2564
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2565
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2566
+ typeRegistry[namedTypeName] = {
2567
+ name: namedTypeName,
1905
2568
  type: objectNode,
1906
- provenance: provenanceForFile(file)
2569
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2570
+ provenance: provenanceForDeclaration(namedDecl, file)
1907
2571
  };
1908
- return { kind: "reference", name: typeName, typeArguments: [] };
2572
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1909
2573
  }
1910
2574
  return objectNode;
1911
2575
  }
1912
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
2576
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1913
2577
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
1914
2578
  (s) => s?.declarations != null && s.declarations.length > 0
1915
2579
  );
@@ -1921,7 +2585,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1921
2585
  const map = /* @__PURE__ */ new Map();
1922
2586
  for (const member of classDecl.members) {
1923
2587
  if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
1924
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
2588
+ const fieldNode = analyzeFieldToIR(
2589
+ member,
2590
+ checker,
2591
+ file,
2592
+ typeRegistry,
2593
+ visiting,
2594
+ extensionRegistry
2595
+ );
1925
2596
  if (fieldNode) {
1926
2597
  map.set(fieldNode.name, {
1927
2598
  constraints: [...fieldNode.constraints],
@@ -1935,7 +2606,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1935
2606
  }
1936
2607
  const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
1937
2608
  if (interfaceDecl) {
1938
- return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
2609
+ return buildFieldNodeInfoMap(
2610
+ interfaceDecl.members,
2611
+ checker,
2612
+ file,
2613
+ typeRegistry,
2614
+ visiting,
2615
+ extensionRegistry
2616
+ );
1939
2617
  }
1940
2618
  const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
1941
2619
  if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
@@ -1944,17 +2622,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1944
2622
  checker,
1945
2623
  file,
1946
2624
  typeRegistry,
1947
- visiting
2625
+ visiting,
2626
+ extensionRegistry
1948
2627
  );
1949
2628
  }
1950
2629
  }
1951
2630
  return null;
1952
2631
  }
1953
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2632
+ function extractArrayElementTypeNode(sourceNode, checker) {
2633
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2634
+ if (typeNode === void 0) {
2635
+ return void 0;
2636
+ }
2637
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2638
+ if (ts4.isArrayTypeNode(resolvedTypeNode)) {
2639
+ return resolvedTypeNode.elementType;
2640
+ }
2641
+ if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
2642
+ return resolvedTypeNode.typeArguments[0];
2643
+ }
2644
+ return void 0;
2645
+ }
2646
+ function extractUnionMemberTypeNodes(sourceNode, checker) {
2647
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2648
+ if (!typeNode) {
2649
+ return [];
2650
+ }
2651
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2652
+ return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
2653
+ }
2654
+ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
2655
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
2656
+ return resolveAliasedTypeNode(typeNode.type, checker, visited);
2657
+ }
2658
+ if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
2659
+ return typeNode;
2660
+ }
2661
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2662
+ const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
2663
+ if (aliasDecl === void 0 || visited.has(aliasDecl)) {
2664
+ return typeNode;
2665
+ }
2666
+ visited.add(aliasDecl);
2667
+ return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
2668
+ }
2669
+ function isNullishTypeNode(typeNode) {
2670
+ if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
2671
+ return true;
2672
+ }
2673
+ return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
2674
+ }
2675
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
1954
2676
  const map = /* @__PURE__ */ new Map();
1955
2677
  for (const member of members) {
1956
2678
  if (ts4.isPropertySignature(member)) {
1957
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2679
+ const fieldNode = analyzeInterfacePropertyToIR(
2680
+ member,
2681
+ checker,
2682
+ file,
2683
+ typeRegistry,
2684
+ visiting,
2685
+ extensionRegistry
2686
+ );
1958
2687
  if (fieldNode) {
1959
2688
  map.set(fieldNode.name, {
1960
2689
  constraints: [...fieldNode.constraints],
@@ -1967,7 +2696,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1967
2696
  return map;
1968
2697
  }
1969
2698
  var MAX_ALIAS_CHAIN_DEPTH = 8;
1970
- function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2699
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
1971
2700
  if (!ts4.isTypeReferenceNode(typeNode)) return [];
1972
2701
  if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
1973
2702
  const aliasName = typeNode.typeName.getText();
@@ -1980,8 +2709,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1980
2709
  const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
1981
2710
  if (!aliasDecl) return [];
1982
2711
  if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
1983
- const constraints = extractJSDocConstraintNodes(aliasDecl, file);
1984
- constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
2712
+ const aliasFieldType = resolveTypeNode(
2713
+ checker.getTypeAtLocation(aliasDecl.type),
2714
+ checker,
2715
+ file,
2716
+ {},
2717
+ /* @__PURE__ */ new Set(),
2718
+ aliasDecl.type,
2719
+ extensionRegistry
2720
+ );
2721
+ const constraints = extractJSDocConstraintNodes(
2722
+ aliasDecl,
2723
+ file,
2724
+ makeParseOptions(extensionRegistry, aliasFieldType)
2725
+ );
2726
+ constraints.push(
2727
+ ...extractTypeAliasConstraintNodes(
2728
+ aliasDecl.type,
2729
+ checker,
2730
+ file,
2731
+ extensionRegistry,
2732
+ depth + 1
2733
+ )
2734
+ );
1985
2735
  return constraints;
1986
2736
  }
1987
2737
  function provenanceForNode(node, file) {
@@ -1997,6 +2747,12 @@ function provenanceForNode(node, file) {
1997
2747
  function provenanceForFile(file) {
1998
2748
  return { surface: "tsdoc", file, line: 0, column: 0 };
1999
2749
  }
2750
+ function provenanceForDeclaration(node, file) {
2751
+ if (!node) {
2752
+ return provenanceForFile(file);
2753
+ }
2754
+ return provenanceForNode(node, file);
2755
+ }
2000
2756
  function getNamedTypeName(type) {
2001
2757
  const symbol = type.getSymbol();
2002
2758
  if (symbol?.declarations) {
@@ -2015,6 +2771,20 @@ function getNamedTypeName(type) {
2015
2771
  }
2016
2772
  return null;
2017
2773
  }
2774
+ function getNamedTypeDeclaration(type) {
2775
+ const symbol = type.getSymbol();
2776
+ if (symbol?.declarations) {
2777
+ const decl = symbol.declarations[0];
2778
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2779
+ return decl;
2780
+ }
2781
+ }
2782
+ const aliasSymbol = type.aliasSymbol;
2783
+ if (aliasSymbol?.declarations) {
2784
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2785
+ }
2786
+ return void 0;
2787
+ }
2018
2788
  function analyzeMethod(method, checker) {
2019
2789
  if (!ts4.isIdentifier(method.name)) {
2020
2790
  return null;
@@ -2057,10 +2827,10 @@ function detectFormSpecReference(typeNode) {
2057
2827
  }
2058
2828
 
2059
2829
  // src/generators/class-schema.ts
2060
- function generateClassSchemas(analysis, source) {
2830
+ function generateClassSchemas(analysis, source, options) {
2061
2831
  const ir = canonicalizeTSDoc(analysis, source);
2062
2832
  return {
2063
- jsonSchema: generateJsonSchemaFromIR(ir),
2833
+ jsonSchema: generateJsonSchemaFromIR(ir, options),
2064
2834
  uiSchema: generateUiSchemaFromIR(ir)
2065
2835
  };
2066
2836
  }
@@ -2070,27 +2840,54 @@ function generateSchemasFromClass(options) {
2070
2840
  if (!classDecl) {
2071
2841
  throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
2072
2842
  }
2073
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2074
- return generateClassSchemas(analysis, { file: options.filePath });
2843
+ const analysis = analyzeClassToIR(
2844
+ classDecl,
2845
+ ctx.checker,
2846
+ options.filePath,
2847
+ options.extensionRegistry
2848
+ );
2849
+ return generateClassSchemas(
2850
+ analysis,
2851
+ { file: options.filePath },
2852
+ {
2853
+ extensionRegistry: options.extensionRegistry,
2854
+ vendorPrefix: options.vendorPrefix
2855
+ }
2856
+ );
2075
2857
  }
2076
2858
  function generateSchemas(options) {
2077
2859
  const ctx = createProgramContext(options.filePath);
2078
2860
  const source = { file: options.filePath };
2079
2861
  const classDecl = findClassByName(ctx.sourceFile, options.typeName);
2080
2862
  if (classDecl) {
2081
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2082
- return generateClassSchemas(analysis, source);
2863
+ const analysis = analyzeClassToIR(
2864
+ classDecl,
2865
+ ctx.checker,
2866
+ options.filePath,
2867
+ options.extensionRegistry
2868
+ );
2869
+ return generateClassSchemas(analysis, source, options);
2083
2870
  }
2084
2871
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
2085
2872
  if (interfaceDecl) {
2086
- const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
2087
- return generateClassSchemas(analysis, source);
2873
+ const analysis = analyzeInterfaceToIR(
2874
+ interfaceDecl,
2875
+ ctx.checker,
2876
+ options.filePath,
2877
+ options.extensionRegistry
2878
+ );
2879
+ return generateClassSchemas(analysis, source, options);
2088
2880
  }
2089
2881
  const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
2090
2882
  if (typeAlias) {
2091
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
2883
+ const result = analyzeTypeAliasToIR(
2884
+ typeAlias,
2885
+ ctx.checker,
2886
+ options.filePath,
2887
+ options.extensionRegistry
2888
+ );
2092
2889
  if (result.ok) {
2093
- return generateClassSchemas(result.analysis, source);
2890
+ return generateClassSchemas(result.analysis, source, options);
2094
2891
  }
2095
2892
  throw new Error(result.error);
2096
2893
  }
@@ -2099,6 +2896,205 @@ function generateSchemas(options) {
2099
2896
  );
2100
2897
  }
2101
2898
 
2899
+ // src/generators/mixed-authoring.ts
2900
+ function buildMixedAuthoringSchemas(options) {
2901
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2902
+ const analysis = analyzeNamedType(filePath, typeName, schemaOptions.extensionRegistry);
2903
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2904
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2905
+ return {
2906
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2907
+ uiSchema: generateUiSchemaFromIR(ir)
2908
+ };
2909
+ }
2910
+ function analyzeNamedType(filePath, typeName, extensionRegistry) {
2911
+ const ctx = createProgramContext(filePath);
2912
+ const source = { file: filePath };
2913
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2914
+ if (classDecl !== null) {
2915
+ return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
2916
+ }
2917
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2918
+ if (interfaceDecl !== null) {
2919
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
2920
+ }
2921
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2922
+ if (typeAlias !== null) {
2923
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
2924
+ if (result.ok) {
2925
+ return result.analysis;
2926
+ }
2927
+ throw new Error(result.error);
2928
+ }
2929
+ throw new Error(
2930
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2931
+ );
2932
+ }
2933
+ function composeAnalysisWithOverlays(analysis, overlays) {
2934
+ const overlayIR = canonicalizeChainDSL(overlays);
2935
+ const overlayFields = collectOverlayFields(overlayIR.elements);
2936
+ if (overlayFields.length === 0) {
2937
+ return analysis;
2938
+ }
2939
+ const overlayByName = /* @__PURE__ */ new Map();
2940
+ for (const field of overlayFields) {
2941
+ if (overlayByName.has(field.name)) {
2942
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
2943
+ }
2944
+ overlayByName.set(field.name, field);
2945
+ }
2946
+ const mergedFields = [];
2947
+ for (const baseField of analysis.fields) {
2948
+ const overlayField = overlayByName.get(baseField.name);
2949
+ if (overlayField === void 0) {
2950
+ mergedFields.push(baseField);
2951
+ continue;
2952
+ }
2953
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
2954
+ overlayByName.delete(baseField.name);
2955
+ }
2956
+ if (overlayByName.size > 0) {
2957
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
2958
+ throw new Error(
2959
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
2960
+ );
2961
+ }
2962
+ return {
2963
+ ...analysis,
2964
+ fields: mergedFields
2965
+ };
2966
+ }
2967
+ function collectOverlayFields(elements) {
2968
+ const fields = [];
2969
+ for (const element of elements) {
2970
+ switch (element.kind) {
2971
+ case "field":
2972
+ fields.push(element);
2973
+ break;
2974
+ case "group":
2975
+ fields.push(...collectOverlayFields(element.elements));
2976
+ break;
2977
+ case "conditional":
2978
+ fields.push(...collectOverlayFields(element.elements));
2979
+ break;
2980
+ default: {
2981
+ const _exhaustive = element;
2982
+ void _exhaustive;
2983
+ }
2984
+ }
2985
+ }
2986
+ return fields;
2987
+ }
2988
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
2989
+ assertSupportedOverlayField(baseField, overlayField);
2990
+ return {
2991
+ ...baseField,
2992
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
2993
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
2994
+ };
2995
+ }
2996
+ function assertSupportedOverlayField(baseField, overlayField) {
2997
+ if (overlayField.constraints.length > 0) {
2998
+ throw new Error(
2999
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
3000
+ );
3001
+ }
3002
+ if (overlayField.required && !baseField.required) {
3003
+ throw new Error(
3004
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
3005
+ );
3006
+ }
3007
+ }
3008
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
3009
+ const { type: baseType } = baseField;
3010
+ const { type: overlayType } = overlayField;
3011
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
3012
+ throw new Error(
3013
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
3014
+ );
3015
+ }
3016
+ if (overlayType.kind === "dynamic") {
3017
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
3018
+ throw new Error(
3019
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
3020
+ );
3021
+ }
3022
+ return overlayType;
3023
+ }
3024
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
3025
+ throw new Error(
3026
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
3027
+ );
3028
+ }
3029
+ return baseType;
3030
+ }
3031
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
3032
+ const overlayType = overlayField.type;
3033
+ if (overlayType.kind !== "dynamic") {
3034
+ return false;
3035
+ }
3036
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
3037
+ if (resolvedBaseType === null) {
3038
+ return false;
3039
+ }
3040
+ if (overlayType.dynamicKind === "enum") {
3041
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
3042
+ }
3043
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
3044
+ }
3045
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
3046
+ if (type.kind !== "reference") {
3047
+ return type;
3048
+ }
3049
+ if (seen.has(type.name)) {
3050
+ return null;
3051
+ }
3052
+ const definition = typeRegistry[type.name];
3053
+ if (definition === void 0) {
3054
+ return null;
3055
+ }
3056
+ seen.add(type.name);
3057
+ return resolveReferenceType(definition.type, typeRegistry, seen);
3058
+ }
3059
+ function isSameStaticTypeShape(baseType, overlayType) {
3060
+ if (baseType.kind !== overlayType.kind) {
3061
+ return false;
3062
+ }
3063
+ switch (baseType.kind) {
3064
+ case "primitive":
3065
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
3066
+ case "enum":
3067
+ return overlayType.kind === "enum";
3068
+ case "dynamic":
3069
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
3070
+ case "record":
3071
+ return overlayType.kind === "record";
3072
+ case "reference":
3073
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
3074
+ case "union":
3075
+ return overlayType.kind === "union";
3076
+ case "custom":
3077
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
3078
+ case "object":
3079
+ case "array":
3080
+ return true;
3081
+ default: {
3082
+ const _exhaustive = baseType;
3083
+ return _exhaustive;
3084
+ }
3085
+ }
3086
+ }
3087
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
3088
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
3089
+ const overlayOnly = overlayAnnotations.filter(
3090
+ (annotation) => !baseKeys.has(annotationKey(annotation))
3091
+ );
3092
+ return [...baseAnnotations, ...overlayOnly];
3093
+ }
3094
+ function annotationKey(annotation) {
3095
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
3096
+ }
3097
+
2102
3098
  // src/index.ts
2103
3099
  function buildFormSchemas(form, options) {
2104
3100
  return {
@@ -2125,6 +3121,7 @@ function writeSchemas(form, options) {
2125
3121
  // Annotate the CommonJS export names for ESM import in node:
2126
3122
  0 && (module.exports = {
2127
3123
  buildFormSchemas,
3124
+ buildMixedAuthoringSchemas,
2128
3125
  categorizationSchema,
2129
3126
  categorySchema,
2130
3127
  controlSchema,