@formspec/build 0.1.0-alpha.16 → 0.1.0-alpha.19

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 (65) hide show
  1. package/README.md +74 -128
  2. package/dist/__tests__/class-schema.test.d.ts +2 -0
  3. package/dist/__tests__/class-schema.test.d.ts.map +1 -0
  4. package/dist/__tests__/date-extension.integration.test.d.ts +2 -0
  5. package/dist/__tests__/date-extension.integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures/class-schema-regressions.d.ts +83 -0
  7. package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -0
  8. package/dist/__tests__/fixtures/example-date-extension.d.ts +12 -0
  9. package/dist/__tests__/fixtures/example-date-extension.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
  11. package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
  12. package/dist/__tests__/fixtures/extension-forms.d.ts +7 -0
  13. package/dist/__tests__/fixtures/extension-forms.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +1 -0
  15. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -1
  16. package/dist/__tests__/fixtures/named-primitive-aliases.d.ts +15 -0
  17. package/dist/__tests__/fixtures/named-primitive-aliases.d.ts.map +1 -0
  18. package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts +14 -0
  19. package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts.map +1 -0
  20. package/dist/__tests__/fixtures/sample-forms.d.ts +10 -0
  21. package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -1
  22. package/dist/__tests__/generate-schemas.test.d.ts +2 -0
  23. package/dist/__tests__/generate-schemas.test.d.ts.map +1 -0
  24. package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
  25. package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
  26. package/dist/__tests__/parity/parity.test.d.ts +6 -2
  27. package/dist/__tests__/parity/parity.test.d.ts.map +1 -1
  28. package/dist/__tests__/parity/utils.d.ts +9 -4
  29. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  30. package/dist/analyzer/class-analyzer.d.ts +5 -4
  31. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  32. package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
  33. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  34. package/dist/analyzer/program.d.ts +15 -0
  35. package/dist/analyzer/program.d.ts.map +1 -1
  36. package/dist/analyzer/tsdoc-parser.d.ts +23 -2
  37. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  38. package/dist/browser.cjs +269 -11
  39. package/dist/browser.cjs.map +1 -1
  40. package/dist/browser.js +269 -11
  41. package/dist/browser.js.map +1 -1
  42. package/dist/build.d.ts +28 -2
  43. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  44. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  45. package/dist/cli.cjs +1640 -282
  46. package/dist/cli.cjs.map +1 -1
  47. package/dist/cli.js +1638 -281
  48. package/dist/cli.js.map +1 -1
  49. package/dist/extensions/registry.d.ts +25 -1
  50. package/dist/extensions/registry.d.ts.map +1 -1
  51. package/dist/generators/class-schema.d.ts +4 -4
  52. package/dist/generators/class-schema.d.ts.map +1 -1
  53. package/dist/generators/method-schema.d.ts.map +1 -1
  54. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  55. package/dist/index.cjs +1615 -271
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.js +1615 -271
  58. package/dist/index.js.map +1 -1
  59. package/dist/internals.cjs +990 -236
  60. package/dist/internals.cjs.map +1 -1
  61. package/dist/internals.js +988 -234
  62. package/dist/internals.js.map +1 -1
  63. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  64. package/dist/validate/constraint-validator.d.ts.map +1 -1
  65. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ function canonicalizeChainDSL(form) {
20
20
  kind: "form-ir",
21
21
  irVersion: IR_VERSION,
22
22
  elements: canonicalizeElements(form.elements),
23
+ rootAnnotations: [],
23
24
  typeRegistry: {},
24
25
  provenance: CHAIN_DSL_PROVENANCE
25
26
  };
@@ -325,6 +326,7 @@ function canonicalizeTSDoc(analysis, source) {
325
326
  irVersion: IR_VERSION2,
326
327
  elements,
327
328
  typeRegistry: analysis.typeRegistry,
329
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
328
330
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
329
331
  provenance
330
332
  };
@@ -402,6 +404,9 @@ function generateJsonSchemaFromIR(ir, options) {
402
404
  const ctx = makeContext(options);
403
405
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
404
406
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
407
+ if (typeDef.constraints && typeDef.constraints.length > 0) {
408
+ applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
409
+ }
405
410
  if (typeDef.annotations && typeDef.annotations.length > 0) {
406
411
  applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
407
412
  }
@@ -570,7 +575,9 @@ function generateTypeNode(type, ctx) {
570
575
  }
571
576
  }
572
577
  function generatePrimitiveType(type) {
573
- return { type: type.primitiveKind };
578
+ return {
579
+ type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
580
+ };
574
581
  }
575
582
  function generateEnumType(type) {
576
583
  const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
@@ -743,7 +750,7 @@ function applyAnnotations(schema, annotations, ctx) {
743
750
  case "deprecated":
744
751
  schema.deprecated = true;
745
752
  if (annotation.message !== void 0 && annotation.message !== "") {
746
- schema["x-formspec-deprecation-description"] = annotation.message;
753
+ schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
747
754
  }
748
755
  break;
749
756
  case "placeholder":
@@ -776,7 +783,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
776
783
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
777
784
  );
778
785
  }
779
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
786
+ assignVendorPrefixedExtensionKeywords(
787
+ schema,
788
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
789
+ ctx.vendorPrefix,
790
+ `custom constraint "${constraint.constraintId}"`
791
+ );
780
792
  }
781
793
  function applyCustomAnnotation(schema, annotation, ctx) {
782
794
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -788,7 +800,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
788
800
  if (registration.toJsonSchema === void 0) {
789
801
  return;
790
802
  }
791
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
803
+ assignVendorPrefixedExtensionKeywords(
804
+ schema,
805
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
806
+ ctx.vendorPrefix,
807
+ `custom annotation "${annotation.annotationId}"`
808
+ );
809
+ }
810
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
811
+ for (const [key, value] of Object.entries(extensionSchema)) {
812
+ if (!key.startsWith(`${vendorPrefix}-`)) {
813
+ throw new Error(
814
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
815
+ );
816
+ }
817
+ schema[key] = value;
818
+ }
792
819
  }
793
820
 
794
821
  // src/json-schema/generator.ts
@@ -1041,7 +1068,10 @@ function getSchemaExtension(schema, key) {
1041
1068
  // src/extensions/registry.ts
1042
1069
  function createExtensionRegistry(extensions) {
1043
1070
  const typeMap = /* @__PURE__ */ new Map();
1071
+ const typeNameMap = /* @__PURE__ */ new Map();
1044
1072
  const constraintMap = /* @__PURE__ */ new Map();
1073
+ const constraintTagMap = /* @__PURE__ */ new Map();
1074
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
1045
1075
  const annotationMap = /* @__PURE__ */ new Map();
1046
1076
  for (const ext of extensions) {
1047
1077
  if (ext.types !== void 0) {
@@ -1051,6 +1081,27 @@ function createExtensionRegistry(extensions) {
1051
1081
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1052
1082
  }
1053
1083
  typeMap.set(qualifiedId, type);
1084
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
1085
+ if (typeNameMap.has(sourceTypeName)) {
1086
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
1087
+ }
1088
+ typeNameMap.set(sourceTypeName, {
1089
+ extensionId: ext.extensionId,
1090
+ registration: type
1091
+ });
1092
+ }
1093
+ if (type.builtinConstraintBroadenings !== void 0) {
1094
+ for (const broadening of type.builtinConstraintBroadenings) {
1095
+ const key = `${qualifiedId}:${broadening.tagName}`;
1096
+ if (builtinBroadeningMap.has(key)) {
1097
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
1098
+ }
1099
+ builtinBroadeningMap.set(key, {
1100
+ extensionId: ext.extensionId,
1101
+ registration: broadening
1102
+ });
1103
+ }
1104
+ }
1054
1105
  }
1055
1106
  }
1056
1107
  if (ext.constraints !== void 0) {
@@ -1062,6 +1113,17 @@ function createExtensionRegistry(extensions) {
1062
1113
  constraintMap.set(qualifiedId, constraint);
1063
1114
  }
1064
1115
  }
1116
+ if (ext.constraintTags !== void 0) {
1117
+ for (const tag of ext.constraintTags) {
1118
+ if (constraintTagMap.has(tag.tagName)) {
1119
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
1120
+ }
1121
+ constraintTagMap.set(tag.tagName, {
1122
+ extensionId: ext.extensionId,
1123
+ registration: tag
1124
+ });
1125
+ }
1126
+ }
1065
1127
  if (ext.annotations !== void 0) {
1066
1128
  for (const annotation of ext.annotations) {
1067
1129
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -1075,7 +1137,10 @@ function createExtensionRegistry(extensions) {
1075
1137
  return {
1076
1138
  extensions,
1077
1139
  findType: (typeId) => typeMap.get(typeId),
1140
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
1078
1141
  findConstraint: (constraintId) => constraintMap.get(constraintId),
1142
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
1143
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
1079
1144
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
1080
1145
  };
1081
1146
  }
@@ -1143,85 +1208,17 @@ var jsonSchema7Schema = z3.lazy(
1143
1208
  );
1144
1209
 
1145
1210
  // src/analyzer/program.ts
1146
- import * as ts from "typescript";
1211
+ import * as ts4 from "typescript";
1147
1212
  import * as path from "path";
1148
- function createProgramContext(filePath) {
1149
- const absolutePath = path.resolve(filePath);
1150
- const fileDir = path.dirname(absolutePath);
1151
- const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
1152
- let compilerOptions;
1153
- let fileNames;
1154
- if (configPath) {
1155
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
1156
- if (configFile.error) {
1157
- throw new Error(
1158
- `Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
1159
- );
1160
- }
1161
- const parsed = ts.parseJsonConfigFileContent(
1162
- configFile.config,
1163
- ts.sys,
1164
- path.dirname(configPath)
1165
- );
1166
- if (parsed.errors.length > 0) {
1167
- const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
1168
- throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
1169
- }
1170
- compilerOptions = parsed.options;
1171
- fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
1172
- } else {
1173
- compilerOptions = {
1174
- target: ts.ScriptTarget.ES2022,
1175
- module: ts.ModuleKind.NodeNext,
1176
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
1177
- strict: true,
1178
- skipLibCheck: true,
1179
- declaration: true
1180
- };
1181
- fileNames = [absolutePath];
1182
- }
1183
- const program = ts.createProgram(fileNames, compilerOptions);
1184
- const sourceFile = program.getSourceFile(absolutePath);
1185
- if (!sourceFile) {
1186
- throw new Error(`Could not find source file: ${absolutePath}`);
1187
- }
1188
- return {
1189
- program,
1190
- checker: program.getTypeChecker(),
1191
- sourceFile
1192
- };
1193
- }
1194
- function findNodeByName(sourceFile, name, predicate, getName) {
1195
- let result = null;
1196
- function visit(node) {
1197
- if (result) return;
1198
- if (predicate(node) && getName(node) === name) {
1199
- result = node;
1200
- return;
1201
- }
1202
- ts.forEachChild(node, visit);
1203
- }
1204
- visit(sourceFile);
1205
- return result;
1206
- }
1207
- function findClassByName(sourceFile, className) {
1208
- return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
1209
- }
1210
- function findInterfaceByName(sourceFile, interfaceName) {
1211
- return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
1212
- }
1213
- function findTypeAliasByName(sourceFile, aliasName) {
1214
- return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
1215
- }
1216
1213
 
1217
1214
  // src/analyzer/class-analyzer.ts
1218
- import * as ts4 from "typescript";
1215
+ import * as ts3 from "typescript";
1219
1216
 
1220
1217
  // src/analyzer/jsdoc-constraints.ts
1221
- import * as ts3 from "typescript";
1218
+ import * as ts2 from "typescript";
1222
1219
 
1223
1220
  // src/analyzer/tsdoc-parser.ts
1224
- import * as ts2 from "typescript";
1221
+ import * as ts from "typescript";
1225
1222
  import {
1226
1223
  TSDocParser,
1227
1224
  TSDocConfiguration,
@@ -1261,7 +1258,7 @@ var LENGTH_CONSTRAINT_MAP = {
1261
1258
  maxItems: "maxItems"
1262
1259
  };
1263
1260
  var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1264
- function createFormSpecTSDocConfig() {
1261
+ function createFormSpecTSDocConfig(extensionTagNames = []) {
1265
1262
  const config = new TSDocConfiguration();
1266
1263
  for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
1267
1264
  config.addTagDefinition(
@@ -1281,14 +1278,34 @@ function createFormSpecTSDocConfig() {
1281
1278
  })
1282
1279
  );
1283
1280
  }
1281
+ for (const tagName of extensionTagNames) {
1282
+ config.addTagDefinition(
1283
+ new TSDocTagDefinition({
1284
+ tagName: "@" + tagName,
1285
+ syntaxKind: TSDocTagSyntaxKind.BlockTag,
1286
+ allowMultiple: true
1287
+ })
1288
+ );
1289
+ }
1284
1290
  return config;
1285
1291
  }
1286
- var sharedParser;
1287
- function getParser() {
1288
- sharedParser ??= new TSDocParser(createFormSpecTSDocConfig());
1289
- return sharedParser;
1290
- }
1291
- function parseTSDocTags(node, file = "") {
1292
+ var parserCache = /* @__PURE__ */ new Map();
1293
+ function getParser(options) {
1294
+ const extensionTagNames = [
1295
+ ...options?.extensionRegistry?.extensions.flatMap(
1296
+ (extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
1297
+ ) ?? []
1298
+ ].sort();
1299
+ const cacheKey = extensionTagNames.join("|");
1300
+ const existing = parserCache.get(cacheKey);
1301
+ if (existing) {
1302
+ return existing;
1303
+ }
1304
+ const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
1305
+ parserCache.set(cacheKey, parser);
1306
+ return parser;
1307
+ }
1308
+ function parseTSDocTags(node, file = "", options) {
1292
1309
  const constraints = [];
1293
1310
  const annotations = [];
1294
1311
  let displayName;
@@ -1299,17 +1316,17 @@ function parseTSDocTags(node, file = "") {
1299
1316
  let placeholderProvenance;
1300
1317
  const sourceFile = node.getSourceFile();
1301
1318
  const sourceText = sourceFile.getFullText();
1302
- const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
1319
+ const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
1303
1320
  if (commentRanges) {
1304
1321
  for (const range of commentRanges) {
1305
- if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
1322
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
1306
1323
  continue;
1307
1324
  }
1308
1325
  const commentText = sourceText.substring(range.pos, range.end);
1309
1326
  if (!commentText.startsWith("/**")) {
1310
1327
  continue;
1311
1328
  }
1312
- const parser = getParser();
1329
+ const parser = getParser(options);
1313
1330
  const parserContext = parser.parseRange(
1314
1331
  TextRange.fromStringRange(sourceText, range.pos, range.end)
1315
1332
  );
@@ -1320,26 +1337,31 @@ function parseTSDocTags(node, file = "") {
1320
1337
  const text2 = extractBlockText(block).trim();
1321
1338
  if (text2 === "") continue;
1322
1339
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1323
- if (tagName === "displayName") {
1324
- if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1325
- displayName = text2;
1326
- displayNameProvenance = provenance2;
1327
- }
1328
- } else if (tagName === "format") {
1329
- annotations.push({
1330
- kind: "annotation",
1331
- annotationKind: "format",
1332
- value: text2,
1333
- provenance: provenance2
1334
- });
1335
- } else {
1336
- if (tagName === "description" && description === void 0) {
1340
+ switch (tagName) {
1341
+ case "displayName":
1342
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1343
+ displayName = text2;
1344
+ displayNameProvenance = provenance2;
1345
+ }
1346
+ break;
1347
+ case "format":
1348
+ annotations.push({
1349
+ kind: "annotation",
1350
+ annotationKind: "format",
1351
+ value: text2,
1352
+ provenance: provenance2
1353
+ });
1354
+ break;
1355
+ case "description":
1337
1356
  description = text2;
1338
1357
  descriptionProvenance = provenance2;
1339
- } else if (tagName === "placeholder" && placeholder === void 0) {
1340
- placeholder = text2;
1341
- placeholderProvenance = provenance2;
1342
- }
1358
+ break;
1359
+ case "placeholder":
1360
+ if (placeholder === void 0) {
1361
+ placeholder = text2;
1362
+ placeholderProvenance = provenance2;
1363
+ }
1364
+ break;
1343
1365
  }
1344
1366
  continue;
1345
1367
  }
@@ -1348,7 +1370,7 @@ function parseTSDocTags(node, file = "") {
1348
1370
  const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1349
1371
  if (text === "" && expectedType !== "boolean") continue;
1350
1372
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1351
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1373
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1352
1374
  if (constraintNode) {
1353
1375
  constraints.push(constraintNode);
1354
1376
  }
@@ -1369,6 +1391,13 @@ function parseTSDocTags(node, file = "") {
1369
1391
  descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1370
1392
  }
1371
1393
  }
1394
+ if (description === void 0) {
1395
+ const summary = extractPlainText(docComment.summarySection).trim();
1396
+ if (summary !== "") {
1397
+ description = summary;
1398
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
1399
+ }
1400
+ }
1372
1401
  }
1373
1402
  }
1374
1403
  if (displayName !== void 0 && displayNameProvenance !== void 0) {
@@ -1395,7 +1424,7 @@ function parseTSDocTags(node, file = "") {
1395
1424
  provenance: placeholderProvenance
1396
1425
  });
1397
1426
  }
1398
- const jsDocTagsAll = ts2.getJSDocTags(node);
1427
+ const jsDocTagsAll = ts.getJSDocTags(node);
1399
1428
  for (const tag of jsDocTagsAll) {
1400
1429
  const tagName = normalizeConstraintTagName(tag.tagName.text);
1401
1430
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
@@ -1408,7 +1437,7 @@ function parseTSDocTags(node, file = "") {
1408
1437
  annotations.push(defaultValueNode);
1409
1438
  continue;
1410
1439
  }
1411
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1440
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1412
1441
  if (constraintNode) {
1413
1442
  constraints.push(constraintNode);
1414
1443
  }
@@ -1418,7 +1447,7 @@ function parseTSDocTags(node, file = "") {
1418
1447
  function extractDisplayNameMetadata(node) {
1419
1448
  let displayName;
1420
1449
  const memberDisplayNames = /* @__PURE__ */ new Map();
1421
- for (const tag of ts2.getJSDocTags(node)) {
1450
+ for (const tag of ts.getJSDocTags(node)) {
1422
1451
  const tagName = normalizeConstraintTagName(tag.tagName.text);
1423
1452
  if (tagName !== "displayName") continue;
1424
1453
  const commentText = getTagCommentText(tag);
@@ -1439,11 +1468,11 @@ function extractDisplayNameMetadata(node) {
1439
1468
  }
1440
1469
  function extractPathTarget(text) {
1441
1470
  const trimmed = text.trimStart();
1442
- const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
1443
- if (!match?.[1] || !match[2]) return null;
1471
+ const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
1472
+ if (!match?.[1]) return null;
1444
1473
  return {
1445
1474
  path: { segments: [match[1]] },
1446
- remainingText: match[2]
1475
+ remainingText: match[2] ?? ""
1447
1476
  };
1448
1477
  }
1449
1478
  function extractBlockText(block) {
@@ -1464,7 +1493,11 @@ function extractPlainText(node) {
1464
1493
  }
1465
1494
  return result;
1466
1495
  }
1467
- function parseConstraintValue(tagName, text, provenance) {
1496
+ function parseConstraintValue(tagName, text, provenance, options) {
1497
+ const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
1498
+ if (customConstraint) {
1499
+ return customConstraint;
1500
+ }
1468
1501
  if (!isBuiltinConstraintName(tagName)) {
1469
1502
  return null;
1470
1503
  }
@@ -1569,6 +1602,83 @@ function parseConstraintValue(tagName, text, provenance) {
1569
1602
  provenance
1570
1603
  };
1571
1604
  }
1605
+ function parseExtensionConstraintValue(tagName, text, provenance, options) {
1606
+ const pathResult = extractPathTarget(text);
1607
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1608
+ const path3 = pathResult?.path;
1609
+ const registry = options?.extensionRegistry;
1610
+ if (registry === void 0) {
1611
+ return null;
1612
+ }
1613
+ const directTag = registry.findConstraintTag(tagName);
1614
+ if (directTag !== void 0) {
1615
+ return makeCustomConstraintNode(
1616
+ directTag.extensionId,
1617
+ directTag.registration.constraintName,
1618
+ directTag.registration.parseValue(effectiveText),
1619
+ provenance,
1620
+ path3,
1621
+ registry
1622
+ );
1623
+ }
1624
+ if (!isBuiltinConstraintName(tagName)) {
1625
+ return null;
1626
+ }
1627
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
1628
+ if (broadenedTypeId === void 0) {
1629
+ return null;
1630
+ }
1631
+ const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
1632
+ if (broadened === void 0) {
1633
+ return null;
1634
+ }
1635
+ return makeCustomConstraintNode(
1636
+ broadened.extensionId,
1637
+ broadened.registration.constraintName,
1638
+ broadened.registration.parseValue(effectiveText),
1639
+ provenance,
1640
+ path3,
1641
+ registry
1642
+ );
1643
+ }
1644
+ function getBroadenedCustomTypeId(fieldType) {
1645
+ if (fieldType?.kind === "custom") {
1646
+ return fieldType.typeId;
1647
+ }
1648
+ if (fieldType?.kind !== "union") {
1649
+ return void 0;
1650
+ }
1651
+ const customMembers = fieldType.members.filter(
1652
+ (member) => member.kind === "custom"
1653
+ );
1654
+ if (customMembers.length !== 1) {
1655
+ return void 0;
1656
+ }
1657
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
1658
+ const allOtherMembersAreNull = nonCustomMembers.every(
1659
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
1660
+ );
1661
+ const customMember = customMembers[0];
1662
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
1663
+ }
1664
+ function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path3, registry) {
1665
+ const constraintId = `${extensionId}/${constraintName}`;
1666
+ const registration = registry.findConstraint(constraintId);
1667
+ if (registration === void 0) {
1668
+ throw new Error(
1669
+ `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
1670
+ );
1671
+ }
1672
+ return {
1673
+ kind: "constraint",
1674
+ constraintKind: "custom",
1675
+ constraintId,
1676
+ payload,
1677
+ compositionRule: registration.compositionRule,
1678
+ ...path3 && { path: path3 },
1679
+ provenance
1680
+ };
1681
+ }
1572
1682
  function parseDefaultValueValue(text, provenance) {
1573
1683
  const trimmed = text.trim();
1574
1684
  let value;
@@ -1625,33 +1735,33 @@ function getTagCommentText(tag) {
1625
1735
  if (typeof tag.comment === "string") {
1626
1736
  return tag.comment;
1627
1737
  }
1628
- return ts2.getTextOfJSDocComment(tag.comment);
1738
+ return ts.getTextOfJSDocComment(tag.comment);
1629
1739
  }
1630
1740
 
1631
1741
  // src/analyzer/jsdoc-constraints.ts
1632
- function extractJSDocConstraintNodes(node, file = "") {
1633
- const result = parseTSDocTags(node, file);
1742
+ function extractJSDocConstraintNodes(node, file = "", options) {
1743
+ const result = parseTSDocTags(node, file, options);
1634
1744
  return [...result.constraints];
1635
1745
  }
1636
- function extractJSDocAnnotationNodes(node, file = "") {
1637
- const result = parseTSDocTags(node, file);
1746
+ function extractJSDocAnnotationNodes(node, file = "", options) {
1747
+ const result = parseTSDocTags(node, file, options);
1638
1748
  return [...result.annotations];
1639
1749
  }
1640
1750
  function extractDefaultValueAnnotation(initializer, file = "") {
1641
1751
  if (!initializer) return null;
1642
1752
  let value;
1643
- if (ts3.isStringLiteral(initializer)) {
1753
+ if (ts2.isStringLiteral(initializer)) {
1644
1754
  value = initializer.text;
1645
- } else if (ts3.isNumericLiteral(initializer)) {
1755
+ } else if (ts2.isNumericLiteral(initializer)) {
1646
1756
  value = Number(initializer.text);
1647
- } else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
1757
+ } else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
1648
1758
  value = true;
1649
- } else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
1759
+ } else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
1650
1760
  value = false;
1651
- } else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
1761
+ } else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
1652
1762
  value = null;
1653
- } else if (ts3.isPrefixUnaryExpression(initializer)) {
1654
- if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
1763
+ } else if (ts2.isPrefixUnaryExpression(initializer)) {
1764
+ if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
1655
1765
  value = -Number(initializer.operand.text);
1656
1766
  }
1657
1767
  }
@@ -1673,36 +1783,56 @@ function extractDefaultValueAnnotation(initializer, file = "") {
1673
1783
 
1674
1784
  // src/analyzer/class-analyzer.ts
1675
1785
  function isObjectType(type) {
1676
- return !!(type.flags & ts4.TypeFlags.Object);
1786
+ return !!(type.flags & ts3.TypeFlags.Object);
1677
1787
  }
1678
1788
  function isTypeReference(type) {
1679
- return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1789
+ return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
1680
1790
  }
1681
1791
  var RESOLVING_TYPE_PLACEHOLDER = {
1682
1792
  kind: "object",
1683
1793
  properties: [],
1684
1794
  additionalProperties: true
1685
1795
  };
1686
- function analyzeClassToIR(classDecl, checker, file = "") {
1796
+ function makeParseOptions(extensionRegistry, fieldType) {
1797
+ if (extensionRegistry === void 0 && fieldType === void 0) {
1798
+ return void 0;
1799
+ }
1800
+ return {
1801
+ ...extensionRegistry !== void 0 && { extensionRegistry },
1802
+ ...fieldType !== void 0 && { fieldType }
1803
+ };
1804
+ }
1805
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1687
1806
  const name = classDecl.name?.text ?? "AnonymousClass";
1688
1807
  const fields = [];
1689
1808
  const fieldLayouts = [];
1690
1809
  const typeRegistry = {};
1691
- const annotations = extractJSDocAnnotationNodes(classDecl, file);
1810
+ const annotations = extractJSDocAnnotationNodes(
1811
+ classDecl,
1812
+ file,
1813
+ makeParseOptions(extensionRegistry)
1814
+ );
1692
1815
  const visiting = /* @__PURE__ */ new Set();
1693
1816
  const instanceMethods = [];
1694
1817
  const staticMethods = [];
1695
1818
  for (const member of classDecl.members) {
1696
- if (ts4.isPropertyDeclaration(member)) {
1697
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1819
+ if (ts3.isPropertyDeclaration(member)) {
1820
+ const fieldNode = analyzeFieldToIR(
1821
+ member,
1822
+ checker,
1823
+ file,
1824
+ typeRegistry,
1825
+ visiting,
1826
+ extensionRegistry
1827
+ );
1698
1828
  if (fieldNode) {
1699
1829
  fields.push(fieldNode);
1700
1830
  fieldLayouts.push({});
1701
1831
  }
1702
- } else if (ts4.isMethodDeclaration(member)) {
1832
+ } else if (ts3.isMethodDeclaration(member)) {
1703
1833
  const methodInfo = analyzeMethod(member, checker);
1704
1834
  if (methodInfo) {
1705
- const isStatic = member.modifiers?.some((m) => m.kind === ts4.SyntaxKind.StaticKeyword);
1835
+ const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
1706
1836
  if (isStatic) {
1707
1837
  staticMethods.push(methodInfo);
1708
1838
  } else {
@@ -1721,15 +1851,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1721
1851
  staticMethods
1722
1852
  };
1723
1853
  }
1724
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1854
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
1725
1855
  const name = interfaceDecl.name.text;
1726
1856
  const fields = [];
1727
1857
  const typeRegistry = {};
1728
- const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1858
+ const annotations = extractJSDocAnnotationNodes(
1859
+ interfaceDecl,
1860
+ file,
1861
+ makeParseOptions(extensionRegistry)
1862
+ );
1729
1863
  const visiting = /* @__PURE__ */ new Set();
1730
1864
  for (const member of interfaceDecl.members) {
1731
- if (ts4.isPropertySignature(member)) {
1732
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1865
+ if (ts3.isPropertySignature(member)) {
1866
+ const fieldNode = analyzeInterfacePropertyToIR(
1867
+ member,
1868
+ checker,
1869
+ file,
1870
+ typeRegistry,
1871
+ visiting,
1872
+ extensionRegistry
1873
+ );
1733
1874
  if (fieldNode) {
1734
1875
  fields.push(fieldNode);
1735
1876
  }
@@ -1746,11 +1887,11 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1746
1887
  staticMethods: []
1747
1888
  };
1748
1889
  }
1749
- function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1750
- if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1890
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1891
+ if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1751
1892
  const sourceFile = typeAlias.getSourceFile();
1752
1893
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
1753
- const kindDesc = ts4.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1894
+ const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1754
1895
  return {
1755
1896
  ok: false,
1756
1897
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
@@ -1759,11 +1900,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1759
1900
  const name = typeAlias.name.text;
1760
1901
  const fields = [];
1761
1902
  const typeRegistry = {};
1762
- const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1903
+ const annotations = extractJSDocAnnotationNodes(
1904
+ typeAlias,
1905
+ file,
1906
+ makeParseOptions(extensionRegistry)
1907
+ );
1763
1908
  const visiting = /* @__PURE__ */ new Set();
1764
1909
  for (const member of typeAlias.type.members) {
1765
- if (ts4.isPropertySignature(member)) {
1766
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1910
+ if (ts3.isPropertySignature(member)) {
1911
+ const fieldNode = analyzeInterfacePropertyToIR(
1912
+ member,
1913
+ checker,
1914
+ file,
1915
+ typeRegistry,
1916
+ visiting,
1917
+ extensionRegistry
1918
+ );
1767
1919
  if (fieldNode) {
1768
1920
  fields.push(fieldNode);
1769
1921
  }
@@ -1782,22 +1934,36 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1782
1934
  }
1783
1935
  };
1784
1936
  }
1785
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1786
- if (!ts4.isIdentifier(prop.name)) {
1937
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1938
+ if (!ts3.isIdentifier(prop.name)) {
1787
1939
  return null;
1788
1940
  }
1789
1941
  const name = prop.name.text;
1790
1942
  const tsType = checker.getTypeAtLocation(prop);
1791
1943
  const optional = prop.questionToken !== void 0;
1792
1944
  const provenance = provenanceForNode(prop, file);
1793
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1945
+ let type = resolveTypeNode(
1946
+ tsType,
1947
+ checker,
1948
+ file,
1949
+ typeRegistry,
1950
+ visiting,
1951
+ prop,
1952
+ extensionRegistry
1953
+ );
1794
1954
  const constraints = [];
1795
- if (prop.type) {
1796
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1955
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
1956
+ constraints.push(
1957
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
1958
+ );
1797
1959
  }
1798
- constraints.push(...extractJSDocConstraintNodes(prop, file));
1960
+ constraints.push(
1961
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
1962
+ );
1799
1963
  let annotations = [];
1800
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
1964
+ annotations.push(
1965
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
1966
+ );
1801
1967
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1802
1968
  if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1803
1969
  annotations.push(defaultAnnotation);
@@ -1813,22 +1979,36 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1813
1979
  provenance
1814
1980
  };
1815
1981
  }
1816
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
1817
- if (!ts4.isIdentifier(prop.name)) {
1982
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1983
+ if (!ts3.isIdentifier(prop.name)) {
1818
1984
  return null;
1819
1985
  }
1820
1986
  const name = prop.name.text;
1821
1987
  const tsType = checker.getTypeAtLocation(prop);
1822
1988
  const optional = prop.questionToken !== void 0;
1823
1989
  const provenance = provenanceForNode(prop, file);
1824
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1990
+ let type = resolveTypeNode(
1991
+ tsType,
1992
+ checker,
1993
+ file,
1994
+ typeRegistry,
1995
+ visiting,
1996
+ prop,
1997
+ extensionRegistry
1998
+ );
1825
1999
  const constraints = [];
1826
- if (prop.type) {
1827
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2000
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
2001
+ constraints.push(
2002
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2003
+ );
1828
2004
  }
1829
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2005
+ constraints.push(
2006
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
2007
+ );
1830
2008
  let annotations = [];
1831
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2009
+ annotations.push(
2010
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2011
+ );
1832
2012
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1833
2013
  return {
1834
2014
  kind: "field",
@@ -1902,20 +2082,94 @@ function parseEnumMemberDisplayName(value) {
1902
2082
  if (label === "") return null;
1903
2083
  return { value: match[1], label };
1904
2084
  }
1905
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1906
- if (type.flags & ts4.TypeFlags.String) {
2085
+ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
2086
+ if (sourceNode === void 0 || extensionRegistry === void 0) {
2087
+ return null;
2088
+ }
2089
+ const typeNode = extractTypeNodeFromSource(sourceNode);
2090
+ if (typeNode === void 0) {
2091
+ return null;
2092
+ }
2093
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
2094
+ }
2095
+ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
2096
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2097
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
2098
+ }
2099
+ const typeName = getTypeNodeRegistrationName(typeNode);
2100
+ if (typeName === null) {
2101
+ return null;
2102
+ }
2103
+ const registration = extensionRegistry.findTypeByName(typeName);
2104
+ if (registration !== void 0) {
2105
+ return {
2106
+ kind: "custom",
2107
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
2108
+ payload: null
2109
+ };
2110
+ }
2111
+ if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
2112
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2113
+ if (aliasDecl !== void 0) {
2114
+ return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
2115
+ }
2116
+ }
2117
+ return null;
2118
+ }
2119
+ function extractTypeNodeFromSource(sourceNode) {
2120
+ if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
2121
+ return sourceNode.type;
2122
+ }
2123
+ if (ts3.isTypeNode(sourceNode)) {
2124
+ return sourceNode;
2125
+ }
2126
+ return void 0;
2127
+ }
2128
+ function getTypeNodeRegistrationName(typeNode) {
2129
+ if (ts3.isTypeReferenceNode(typeNode)) {
2130
+ return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
2131
+ }
2132
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2133
+ return getTypeNodeRegistrationName(typeNode.type);
2134
+ }
2135
+ if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
2136
+ return typeNode.getText();
2137
+ }
2138
+ return null;
2139
+ }
2140
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2141
+ const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2142
+ if (customType) {
2143
+ return customType;
2144
+ }
2145
+ const primitiveAlias = tryResolveNamedPrimitiveAlias(
2146
+ type,
2147
+ checker,
2148
+ file,
2149
+ typeRegistry,
2150
+ visiting,
2151
+ sourceNode,
2152
+ extensionRegistry
2153
+ );
2154
+ if (primitiveAlias) {
2155
+ return primitiveAlias;
2156
+ }
2157
+ if (type.flags & ts3.TypeFlags.String) {
1907
2158
  return { kind: "primitive", primitiveKind: "string" };
1908
2159
  }
1909
- if (type.flags & ts4.TypeFlags.Number) {
2160
+ if (type.flags & ts3.TypeFlags.Number) {
1910
2161
  return { kind: "primitive", primitiveKind: "number" };
1911
2162
  }
1912
- if (type.flags & ts4.TypeFlags.Boolean) {
2163
+ if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
2164
+ return { kind: "primitive", primitiveKind: "bigint" };
2165
+ }
2166
+ if (type.flags & ts3.TypeFlags.Boolean) {
1913
2167
  return { kind: "primitive", primitiveKind: "boolean" };
1914
2168
  }
1915
- if (type.flags & ts4.TypeFlags.Null) {
2169
+ if (type.flags & ts3.TypeFlags.Null) {
1916
2170
  return { kind: "primitive", primitiveKind: "null" };
1917
2171
  }
1918
- if (type.flags & ts4.TypeFlags.Undefined) {
2172
+ if (type.flags & ts3.TypeFlags.Undefined) {
1919
2173
  return { kind: "primitive", primitiveKind: "null" };
1920
2174
  }
1921
2175
  if (type.isStringLiteral()) {
@@ -1931,27 +2185,120 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
1931
2185
  };
1932
2186
  }
1933
2187
  if (type.isUnion()) {
1934
- return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
2188
+ return resolveUnionType(
2189
+ type,
2190
+ checker,
2191
+ file,
2192
+ typeRegistry,
2193
+ visiting,
2194
+ sourceNode,
2195
+ extensionRegistry
2196
+ );
1935
2197
  }
1936
2198
  if (checker.isArrayType(type)) {
1937
- return resolveArrayType(type, checker, file, typeRegistry, visiting);
2199
+ return resolveArrayType(
2200
+ type,
2201
+ checker,
2202
+ file,
2203
+ typeRegistry,
2204
+ visiting,
2205
+ sourceNode,
2206
+ extensionRegistry
2207
+ );
1938
2208
  }
1939
2209
  if (isObjectType(type)) {
1940
- return resolveObjectType(type, checker, file, typeRegistry, visiting);
2210
+ return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
1941
2211
  }
1942
2212
  return { kind: "primitive", primitiveKind: "string" };
1943
2213
  }
1944
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
2214
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2215
+ if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2216
+ return null;
2217
+ }
2218
+ const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
2219
+ if (!aliasDecl) {
2220
+ return null;
2221
+ }
2222
+ const aliasName = aliasDecl.name.text;
2223
+ if (!typeRegistry[aliasName]) {
2224
+ const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
2225
+ const constraints = [
2226
+ ...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
2227
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
2228
+ ];
2229
+ const annotations = extractJSDocAnnotationNodes(
2230
+ aliasDecl,
2231
+ file,
2232
+ makeParseOptions(extensionRegistry)
2233
+ );
2234
+ typeRegistry[aliasName] = {
2235
+ name: aliasName,
2236
+ type: resolveAliasedPrimitiveTarget(
2237
+ aliasType,
2238
+ checker,
2239
+ file,
2240
+ typeRegistry,
2241
+ visiting,
2242
+ extensionRegistry
2243
+ ),
2244
+ ...constraints.length > 0 && { constraints },
2245
+ ...annotations.length > 0 && { annotations },
2246
+ provenance: provenanceForDeclaration(aliasDecl, file)
2247
+ };
2248
+ }
2249
+ return { kind: "reference", name: aliasName, typeArguments: [] };
2250
+ }
2251
+ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
2252
+ const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
2253
+ if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
2254
+ return void 0;
2255
+ }
2256
+ return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2257
+ }
2258
+ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2259
+ if (!ts3.isTypeReferenceNode(typeNode)) {
2260
+ return false;
2261
+ }
2262
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2263
+ if (!aliasDecl) {
2264
+ return false;
2265
+ }
2266
+ const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2267
+ return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2268
+ }
2269
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2270
+ const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2271
+ if (nestedAliasDecl !== void 0) {
2272
+ return resolveAliasedPrimitiveTarget(
2273
+ checker.getTypeFromTypeNode(nestedAliasDecl.type),
2274
+ checker,
2275
+ file,
2276
+ typeRegistry,
2277
+ visiting,
2278
+ extensionRegistry
2279
+ );
2280
+ }
2281
+ return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
2282
+ }
2283
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1945
2284
  const typeName = getNamedTypeName(type);
1946
2285
  const namedDecl = getNamedTypeDeclaration(type);
1947
2286
  if (typeName && typeName in typeRegistry) {
1948
2287
  return { kind: "reference", name: typeName, typeArguments: [] };
1949
2288
  }
1950
2289
  const allTypes = type.types;
2290
+ const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
2291
+ const nonNullSourceNodes = unionMemberTypeNodes.filter(
2292
+ (memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
2293
+ );
1951
2294
  const nonNullTypes = allTypes.filter(
1952
- (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
2295
+ (memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
1953
2296
  );
1954
- const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2297
+ const nonNullMembers = nonNullTypes.map((memberType, index) => ({
2298
+ memberType,
2299
+ sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
2300
+ }));
2301
+ const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
1955
2302
  const memberDisplayNames = /* @__PURE__ */ new Map();
1956
2303
  if (namedDecl) {
1957
2304
  for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
@@ -1967,7 +2314,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1967
2314
  if (!typeName) {
1968
2315
  return result;
1969
2316
  }
1970
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2317
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
1971
2318
  typeRegistry[typeName] = {
1972
2319
  name: typeName,
1973
2320
  type: result,
@@ -1980,7 +2327,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1980
2327
  const displayName = memberDisplayNames.get(String(value));
1981
2328
  return displayName !== void 0 ? { value, displayName } : { value };
1982
2329
  });
1983
- const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
2330
+ const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
1984
2331
  if (isBooleanUnion2) {
1985
2332
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1986
2333
  const result = hasNull ? {
@@ -2015,14 +2362,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2015
2362
  } : enumNode;
2016
2363
  return registerNamed(result);
2017
2364
  }
2018
- if (nonNullTypes.length === 1 && nonNullTypes[0]) {
2365
+ if (nonNullMembers.length === 1 && nonNullMembers[0]) {
2019
2366
  const inner = resolveTypeNode(
2020
- nonNullTypes[0],
2367
+ nonNullMembers[0].memberType,
2021
2368
  checker,
2022
2369
  file,
2023
2370
  typeRegistry,
2024
2371
  visiting,
2025
- sourceNode
2372
+ nonNullMembers[0].sourceNode ?? sourceNode,
2373
+ extensionRegistry
2026
2374
  );
2027
2375
  const result = hasNull ? {
2028
2376
  kind: "union",
@@ -2030,29 +2378,54 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2030
2378
  } : inner;
2031
2379
  return registerNamed(result);
2032
2380
  }
2033
- const members = nonNullTypes.map(
2034
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
2381
+ const members = nonNullMembers.map(
2382
+ ({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
2383
+ memberType,
2384
+ checker,
2385
+ file,
2386
+ typeRegistry,
2387
+ visiting,
2388
+ memberSourceNode ?? sourceNode,
2389
+ extensionRegistry
2390
+ )
2035
2391
  );
2036
2392
  if (hasNull) {
2037
2393
  members.push({ kind: "primitive", primitiveKind: "null" });
2038
2394
  }
2039
2395
  return registerNamed({ kind: "union", members });
2040
2396
  }
2041
- function resolveArrayType(type, checker, file, typeRegistry, visiting) {
2397
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2042
2398
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2043
2399
  const elementType = typeArgs?.[0];
2044
- const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
2400
+ const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
2401
+ const items = elementType ? resolveTypeNode(
2402
+ elementType,
2403
+ checker,
2404
+ file,
2405
+ typeRegistry,
2406
+ visiting,
2407
+ elementSourceNode,
2408
+ extensionRegistry
2409
+ ) : { kind: "primitive", primitiveKind: "string" };
2045
2410
  return { kind: "array", items };
2046
2411
  }
2047
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2412
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2048
2413
  if (type.getProperties().length > 0) {
2049
2414
  return null;
2050
2415
  }
2051
- const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
2416
+ const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
2052
2417
  if (!indexInfo) {
2053
2418
  return null;
2054
2419
  }
2055
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2420
+ const valueType = resolveTypeNode(
2421
+ indexInfo.type,
2422
+ checker,
2423
+ file,
2424
+ typeRegistry,
2425
+ visiting,
2426
+ void 0,
2427
+ extensionRegistry
2428
+ );
2056
2429
  return { kind: "record", valueType };
2057
2430
  }
2058
2431
  function typeNodeContainsReference(type, targetName) {
@@ -2080,7 +2453,7 @@ function typeNodeContainsReference(type, targetName) {
2080
2453
  }
2081
2454
  }
2082
2455
  }
2083
- function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2456
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2084
2457
  const typeName = getNamedTypeName(type);
2085
2458
  const namedTypeName = typeName ?? void 0;
2086
2459
  const namedDecl = getNamedTypeDeclaration(type);
@@ -2111,7 +2484,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2111
2484
  return { kind: "reference", name: namedTypeName, typeArguments: [] };
2112
2485
  }
2113
2486
  }
2114
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2487
+ const recordNode = tryResolveRecordType(
2488
+ type,
2489
+ checker,
2490
+ file,
2491
+ typeRegistry,
2492
+ visiting,
2493
+ extensionRegistry
2494
+ );
2115
2495
  if (recordNode) {
2116
2496
  visiting.delete(type);
2117
2497
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
@@ -2120,7 +2500,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2120
2500
  clearNamedTypeRegistration();
2121
2501
  return recordNode;
2122
2502
  }
2123
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2503
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2124
2504
  typeRegistry[namedTypeName] = {
2125
2505
  name: namedTypeName,
2126
2506
  type: recordNode,
@@ -2132,19 +2512,27 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2132
2512
  return recordNode;
2133
2513
  }
2134
2514
  const properties = [];
2135
- const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
2515
+ const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
2516
+ type,
2517
+ checker,
2518
+ file,
2519
+ typeRegistry,
2520
+ visiting,
2521
+ extensionRegistry
2522
+ );
2136
2523
  for (const prop of type.getProperties()) {
2137
2524
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2138
2525
  if (!declaration) continue;
2139
2526
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
2140
- const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
2527
+ const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
2141
2528
  const propTypeNode = resolveTypeNode(
2142
2529
  propType,
2143
2530
  checker,
2144
2531
  file,
2145
2532
  typeRegistry,
2146
2533
  visiting,
2147
- declaration
2534
+ declaration,
2535
+ extensionRegistry
2148
2536
  );
2149
2537
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2150
2538
  properties.push({
@@ -2163,7 +2551,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2163
2551
  additionalProperties: true
2164
2552
  };
2165
2553
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2166
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2554
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2167
2555
  typeRegistry[namedTypeName] = {
2168
2556
  name: namedTypeName,
2169
2557
  type: objectNode,
@@ -2174,19 +2562,26 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2174
2562
  }
2175
2563
  return objectNode;
2176
2564
  }
2177
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
2565
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2178
2566
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
2179
2567
  (s) => s?.declarations != null && s.declarations.length > 0
2180
2568
  );
2181
2569
  for (const symbol of symbols) {
2182
2570
  const declarations = symbol.declarations;
2183
2571
  if (!declarations) continue;
2184
- const classDecl = declarations.find(ts4.isClassDeclaration);
2572
+ const classDecl = declarations.find(ts3.isClassDeclaration);
2185
2573
  if (classDecl) {
2186
2574
  const map = /* @__PURE__ */ new Map();
2187
2575
  for (const member of classDecl.members) {
2188
- if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
2189
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
2576
+ if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
2577
+ const fieldNode = analyzeFieldToIR(
2578
+ member,
2579
+ checker,
2580
+ file,
2581
+ typeRegistry,
2582
+ visiting,
2583
+ extensionRegistry
2584
+ );
2190
2585
  if (fieldNode) {
2191
2586
  map.set(fieldNode.name, {
2192
2587
  constraints: [...fieldNode.constraints],
@@ -2198,28 +2593,86 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2198
2593
  }
2199
2594
  return map;
2200
2595
  }
2201
- const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
2596
+ const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
2202
2597
  if (interfaceDecl) {
2203
- return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
2204
- }
2205
- const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
2206
- if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
2598
+ return buildFieldNodeInfoMap(
2599
+ interfaceDecl.members,
2600
+ checker,
2601
+ file,
2602
+ typeRegistry,
2603
+ visiting,
2604
+ extensionRegistry
2605
+ );
2606
+ }
2607
+ const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
2608
+ if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
2207
2609
  return buildFieldNodeInfoMap(
2208
2610
  typeAliasDecl.type.members,
2209
2611
  checker,
2210
2612
  file,
2211
2613
  typeRegistry,
2212
- visiting
2614
+ visiting,
2615
+ extensionRegistry
2213
2616
  );
2214
2617
  }
2215
2618
  }
2216
2619
  return null;
2217
2620
  }
2218
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2621
+ function extractArrayElementTypeNode(sourceNode, checker) {
2622
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2623
+ if (typeNode === void 0) {
2624
+ return void 0;
2625
+ }
2626
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2627
+ if (ts3.isArrayTypeNode(resolvedTypeNode)) {
2628
+ return resolvedTypeNode.elementType;
2629
+ }
2630
+ if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
2631
+ return resolvedTypeNode.typeArguments[0];
2632
+ }
2633
+ return void 0;
2634
+ }
2635
+ function extractUnionMemberTypeNodes(sourceNode, checker) {
2636
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2637
+ if (!typeNode) {
2638
+ return [];
2639
+ }
2640
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2641
+ return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
2642
+ }
2643
+ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
2644
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2645
+ return resolveAliasedTypeNode(typeNode.type, checker, visited);
2646
+ }
2647
+ if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
2648
+ return typeNode;
2649
+ }
2650
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2651
+ const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2652
+ if (aliasDecl === void 0 || visited.has(aliasDecl)) {
2653
+ return typeNode;
2654
+ }
2655
+ visited.add(aliasDecl);
2656
+ return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
2657
+ }
2658
+ function isNullishTypeNode(typeNode) {
2659
+ if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
2660
+ return true;
2661
+ }
2662
+ return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
2663
+ }
2664
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
2219
2665
  const map = /* @__PURE__ */ new Map();
2220
2666
  for (const member of members) {
2221
- if (ts4.isPropertySignature(member)) {
2222
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2667
+ if (ts3.isPropertySignature(member)) {
2668
+ const fieldNode = analyzeInterfacePropertyToIR(
2669
+ member,
2670
+ checker,
2671
+ file,
2672
+ typeRegistry,
2673
+ visiting,
2674
+ extensionRegistry
2675
+ );
2223
2676
  if (fieldNode) {
2224
2677
  map.set(fieldNode.name, {
2225
2678
  constraints: [...fieldNode.constraints],
@@ -2232,8 +2685,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2232
2685
  return map;
2233
2686
  }
2234
2687
  var MAX_ALIAS_CHAIN_DEPTH = 8;
2235
- function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2236
- if (!ts4.isTypeReferenceNode(typeNode)) return [];
2688
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
2689
+ if (!ts3.isTypeReferenceNode(typeNode)) return [];
2237
2690
  if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
2238
2691
  const aliasName = typeNode.typeName.getText();
2239
2692
  throw new Error(
@@ -2242,11 +2695,26 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2242
2695
  }
2243
2696
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2244
2697
  if (!symbol?.declarations) return [];
2245
- const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
2698
+ const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
2246
2699
  if (!aliasDecl) return [];
2247
- if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
2248
- const constraints = extractJSDocConstraintNodes(aliasDecl, file);
2249
- constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
2700
+ if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
2701
+ const aliasFieldType = resolveTypeNode(
2702
+ checker.getTypeAtLocation(aliasDecl.type),
2703
+ checker,
2704
+ file,
2705
+ {},
2706
+ /* @__PURE__ */ new Set(),
2707
+ aliasDecl.type,
2708
+ extensionRegistry
2709
+ );
2710
+ const constraints = extractJSDocConstraintNodes(
2711
+ aliasDecl,
2712
+ file,
2713
+ makeParseOptions(extensionRegistry, aliasFieldType)
2714
+ );
2715
+ constraints.push(
2716
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
2717
+ );
2250
2718
  return constraints;
2251
2719
  }
2252
2720
  function provenanceForNode(node, file) {
@@ -2272,14 +2740,14 @@ function getNamedTypeName(type) {
2272
2740
  const symbol = type.getSymbol();
2273
2741
  if (symbol?.declarations) {
2274
2742
  const decl = symbol.declarations[0];
2275
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2276
- const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
2743
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
2744
+ const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
2277
2745
  if (name) return name;
2278
2746
  }
2279
2747
  }
2280
2748
  const aliasSymbol = type.aliasSymbol;
2281
2749
  if (aliasSymbol?.declarations) {
2282
- const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2750
+ const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
2283
2751
  if (aliasDecl) {
2284
2752
  return aliasDecl.name.text;
2285
2753
  }
@@ -2290,24 +2758,24 @@ function getNamedTypeDeclaration(type) {
2290
2758
  const symbol = type.getSymbol();
2291
2759
  if (symbol?.declarations) {
2292
2760
  const decl = symbol.declarations[0];
2293
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2761
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
2294
2762
  return decl;
2295
2763
  }
2296
2764
  }
2297
2765
  const aliasSymbol = type.aliasSymbol;
2298
2766
  if (aliasSymbol?.declarations) {
2299
- return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2767
+ return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
2300
2768
  }
2301
2769
  return void 0;
2302
2770
  }
2303
2771
  function analyzeMethod(method, checker) {
2304
- if (!ts4.isIdentifier(method.name)) {
2772
+ if (!ts3.isIdentifier(method.name)) {
2305
2773
  return null;
2306
2774
  }
2307
2775
  const name = method.name.text;
2308
2776
  const parameters = [];
2309
2777
  for (const param of method.parameters) {
2310
- if (ts4.isIdentifier(param.name)) {
2778
+ if (ts3.isIdentifier(param.name)) {
2311
2779
  const paramInfo = analyzeParameter(param, checker);
2312
2780
  parameters.push(paramInfo);
2313
2781
  }
@@ -2318,7 +2786,7 @@ function analyzeMethod(method, checker) {
2318
2786
  return { name, parameters, returnTypeNode, returnType };
2319
2787
  }
2320
2788
  function analyzeParameter(param, checker) {
2321
- const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
2789
+ const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
2322
2790
  const typeNode = param.type;
2323
2791
  const type = checker.getTypeAtLocation(param);
2324
2792
  const formSpecExportName = detectFormSpecReference(typeNode);
@@ -2327,67 +2795,966 @@ function analyzeParameter(param, checker) {
2327
2795
  }
2328
2796
  function detectFormSpecReference(typeNode) {
2329
2797
  if (!typeNode) return null;
2330
- if (!ts4.isTypeReferenceNode(typeNode)) return null;
2331
- const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
2798
+ if (!ts3.isTypeReferenceNode(typeNode)) return null;
2799
+ const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
2332
2800
  if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
2333
2801
  const typeArg = typeNode.typeArguments?.[0];
2334
- if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
2335
- if (ts4.isIdentifier(typeArg.exprName)) {
2802
+ if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
2803
+ if (ts3.isIdentifier(typeArg.exprName)) {
2336
2804
  return typeArg.exprName.text;
2337
2805
  }
2338
- if (ts4.isQualifiedName(typeArg.exprName)) {
2806
+ if (ts3.isQualifiedName(typeArg.exprName)) {
2339
2807
  return typeArg.exprName.right.text;
2340
2808
  }
2341
2809
  return null;
2342
2810
  }
2343
2811
 
2812
+ // src/analyzer/program.ts
2813
+ function createProgramContext(filePath) {
2814
+ const absolutePath = path.resolve(filePath);
2815
+ const fileDir = path.dirname(absolutePath);
2816
+ const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
2817
+ let compilerOptions;
2818
+ let fileNames;
2819
+ if (configPath) {
2820
+ const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
2821
+ if (configFile.error) {
2822
+ throw new Error(
2823
+ `Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
2824
+ );
2825
+ }
2826
+ const parsed = ts4.parseJsonConfigFileContent(
2827
+ configFile.config,
2828
+ ts4.sys,
2829
+ path.dirname(configPath)
2830
+ );
2831
+ if (parsed.errors.length > 0) {
2832
+ const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
2833
+ throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
2834
+ }
2835
+ compilerOptions = parsed.options;
2836
+ fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
2837
+ } else {
2838
+ compilerOptions = {
2839
+ target: ts4.ScriptTarget.ES2022,
2840
+ module: ts4.ModuleKind.NodeNext,
2841
+ moduleResolution: ts4.ModuleResolutionKind.NodeNext,
2842
+ strict: true,
2843
+ skipLibCheck: true,
2844
+ declaration: true
2845
+ };
2846
+ fileNames = [absolutePath];
2847
+ }
2848
+ const program = ts4.createProgram(fileNames, compilerOptions);
2849
+ const sourceFile = program.getSourceFile(absolutePath);
2850
+ if (!sourceFile) {
2851
+ throw new Error(`Could not find source file: ${absolutePath}`);
2852
+ }
2853
+ return {
2854
+ program,
2855
+ checker: program.getTypeChecker(),
2856
+ sourceFile
2857
+ };
2858
+ }
2859
+ function findNodeByName(sourceFile, name, predicate, getName) {
2860
+ let result = null;
2861
+ function visit(node) {
2862
+ if (result) return;
2863
+ if (predicate(node) && getName(node) === name) {
2864
+ result = node;
2865
+ return;
2866
+ }
2867
+ ts4.forEachChild(node, visit);
2868
+ }
2869
+ visit(sourceFile);
2870
+ return result;
2871
+ }
2872
+ function findClassByName(sourceFile, className) {
2873
+ return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
2874
+ }
2875
+ function findInterfaceByName(sourceFile, interfaceName) {
2876
+ return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
2877
+ }
2878
+ function findTypeAliasByName(sourceFile, aliasName) {
2879
+ return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
2880
+ }
2881
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
2882
+ const ctx = createProgramContext(filePath);
2883
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2884
+ if (classDecl !== null) {
2885
+ return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
2886
+ }
2887
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2888
+ if (interfaceDecl !== null) {
2889
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
2890
+ }
2891
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2892
+ if (typeAlias !== null) {
2893
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
2894
+ if (result.ok) {
2895
+ return result.analysis;
2896
+ }
2897
+ throw new Error(result.error);
2898
+ }
2899
+ throw new Error(
2900
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2901
+ );
2902
+ }
2903
+
2904
+ // src/validate/constraint-validator.ts
2905
+ import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
2906
+ function addContradiction(ctx, message, primary, related) {
2907
+ ctx.diagnostics.push({
2908
+ code: "CONTRADICTING_CONSTRAINTS",
2909
+ message,
2910
+ severity: "error",
2911
+ primaryLocation: primary,
2912
+ relatedLocations: [related]
2913
+ });
2914
+ }
2915
+ function addTypeMismatch(ctx, message, primary) {
2916
+ ctx.diagnostics.push({
2917
+ code: "TYPE_MISMATCH",
2918
+ message,
2919
+ severity: "error",
2920
+ primaryLocation: primary,
2921
+ relatedLocations: []
2922
+ });
2923
+ }
2924
+ function addUnknownExtension(ctx, message, primary) {
2925
+ ctx.diagnostics.push({
2926
+ code: "UNKNOWN_EXTENSION",
2927
+ message,
2928
+ severity: "warning",
2929
+ primaryLocation: primary,
2930
+ relatedLocations: []
2931
+ });
2932
+ }
2933
+ function addUnknownPathTarget(ctx, message, primary) {
2934
+ ctx.diagnostics.push({
2935
+ code: "UNKNOWN_PATH_TARGET",
2936
+ message,
2937
+ severity: "error",
2938
+ primaryLocation: primary,
2939
+ relatedLocations: []
2940
+ });
2941
+ }
2942
+ function addConstraintBroadening(ctx, message, primary, related) {
2943
+ ctx.diagnostics.push({
2944
+ code: "CONSTRAINT_BROADENING",
2945
+ message,
2946
+ severity: "error",
2947
+ primaryLocation: primary,
2948
+ relatedLocations: [related]
2949
+ });
2950
+ }
2951
+ function getExtensionIdFromConstraintId(constraintId) {
2952
+ const separator = constraintId.lastIndexOf("/");
2953
+ if (separator <= 0) {
2954
+ return null;
2955
+ }
2956
+ return constraintId.slice(0, separator);
2957
+ }
2958
+ function findNumeric(constraints, constraintKind) {
2959
+ return constraints.find((c) => c.constraintKind === constraintKind);
2960
+ }
2961
+ function findLength(constraints, constraintKind) {
2962
+ return constraints.find((c) => c.constraintKind === constraintKind);
2963
+ }
2964
+ function findAllowedMembers(constraints) {
2965
+ return constraints.filter(
2966
+ (c) => c.constraintKind === "allowedMembers"
2967
+ );
2968
+ }
2969
+ function findConstConstraints(constraints) {
2970
+ return constraints.filter(
2971
+ (c) => c.constraintKind === "const"
2972
+ );
2973
+ }
2974
+ function jsonValueEquals(left, right) {
2975
+ if (left === right) {
2976
+ return true;
2977
+ }
2978
+ if (Array.isArray(left) || Array.isArray(right)) {
2979
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
2980
+ return false;
2981
+ }
2982
+ return left.every((item, index) => jsonValueEquals(item, right[index]));
2983
+ }
2984
+ if (isJsonObject(left) || isJsonObject(right)) {
2985
+ if (!isJsonObject(left) || !isJsonObject(right)) {
2986
+ return false;
2987
+ }
2988
+ const leftKeys = Object.keys(left).sort();
2989
+ const rightKeys = Object.keys(right).sort();
2990
+ if (leftKeys.length !== rightKeys.length) {
2991
+ return false;
2992
+ }
2993
+ return leftKeys.every((key, index) => {
2994
+ const rightKey = rightKeys[index];
2995
+ if (rightKey !== key) {
2996
+ return false;
2997
+ }
2998
+ const leftValue = left[key];
2999
+ const rightValue = right[rightKey];
3000
+ return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
3001
+ });
3002
+ }
3003
+ return false;
3004
+ }
3005
+ function isJsonObject(value) {
3006
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3007
+ }
3008
+ function isOrderedBoundConstraint(constraint) {
3009
+ return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
3010
+ }
3011
+ function pathKey(constraint) {
3012
+ return constraint.path?.segments.join(".") ?? "";
3013
+ }
3014
+ function orderedBoundFamily(kind) {
3015
+ switch (kind) {
3016
+ case "minimum":
3017
+ case "exclusiveMinimum":
3018
+ return "numeric-lower";
3019
+ case "maximum":
3020
+ case "exclusiveMaximum":
3021
+ return "numeric-upper";
3022
+ case "minLength":
3023
+ return "minLength";
3024
+ case "minItems":
3025
+ return "minItems";
3026
+ case "maxLength":
3027
+ return "maxLength";
3028
+ case "maxItems":
3029
+ return "maxItems";
3030
+ default: {
3031
+ const _exhaustive = kind;
3032
+ return _exhaustive;
3033
+ }
3034
+ }
3035
+ }
3036
+ function isNumericLowerKind(kind) {
3037
+ return kind === "minimum" || kind === "exclusiveMinimum";
3038
+ }
3039
+ function isNumericUpperKind(kind) {
3040
+ return kind === "maximum" || kind === "exclusiveMaximum";
3041
+ }
3042
+ function describeConstraintTag(constraint) {
3043
+ return `@${constraint.constraintKind}`;
3044
+ }
3045
+ function compareConstraintStrength(current, previous) {
3046
+ const family = orderedBoundFamily(current.constraintKind);
3047
+ if (family === "numeric-lower") {
3048
+ if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
3049
+ throw new Error("numeric-lower family received non-numeric lower-bound constraint");
3050
+ }
3051
+ if (current.value !== previous.value) {
3052
+ return current.value > previous.value ? 1 : -1;
3053
+ }
3054
+ if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
3055
+ return 1;
3056
+ }
3057
+ if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
3058
+ return -1;
3059
+ }
3060
+ return 0;
3061
+ }
3062
+ if (family === "numeric-upper") {
3063
+ if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
3064
+ throw new Error("numeric-upper family received non-numeric upper-bound constraint");
3065
+ }
3066
+ if (current.value !== previous.value) {
3067
+ return current.value < previous.value ? 1 : -1;
3068
+ }
3069
+ if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
3070
+ return 1;
3071
+ }
3072
+ if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
3073
+ return -1;
3074
+ }
3075
+ return 0;
3076
+ }
3077
+ switch (family) {
3078
+ case "minLength":
3079
+ case "minItems":
3080
+ if (current.value === previous.value) {
3081
+ return 0;
3082
+ }
3083
+ return current.value > previous.value ? 1 : -1;
3084
+ case "maxLength":
3085
+ case "maxItems":
3086
+ if (current.value === previous.value) {
3087
+ return 0;
3088
+ }
3089
+ return current.value < previous.value ? 1 : -1;
3090
+ default: {
3091
+ const _exhaustive = family;
3092
+ return _exhaustive;
3093
+ }
3094
+ }
3095
+ }
3096
+ function checkConstraintBroadening(ctx, fieldName, constraints) {
3097
+ const strongestByKey = /* @__PURE__ */ new Map();
3098
+ for (const constraint of constraints) {
3099
+ if (!isOrderedBoundConstraint(constraint)) {
3100
+ continue;
3101
+ }
3102
+ const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
3103
+ const previous = strongestByKey.get(key);
3104
+ if (previous === void 0) {
3105
+ strongestByKey.set(key, constraint);
3106
+ continue;
3107
+ }
3108
+ const strength = compareConstraintStrength(constraint, previous);
3109
+ if (strength < 0) {
3110
+ const displayFieldName = formatPathTargetFieldName(
3111
+ fieldName,
3112
+ constraint.path?.segments ?? []
3113
+ );
3114
+ addConstraintBroadening(
3115
+ ctx,
3116
+ `Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
3117
+ constraint.provenance,
3118
+ previous.provenance
3119
+ );
3120
+ continue;
3121
+ }
3122
+ if (strength <= 0) {
3123
+ continue;
3124
+ }
3125
+ strongestByKey.set(key, constraint);
3126
+ }
3127
+ }
3128
+ function compareCustomConstraintStrength(current, previous) {
3129
+ const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
3130
+ const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
3131
+ switch (current.role.bound) {
3132
+ case "lower":
3133
+ return equalPayloadTiebreaker;
3134
+ case "upper":
3135
+ return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
3136
+ case "exact":
3137
+ return order === 0 ? 0 : Number.NaN;
3138
+ default: {
3139
+ const _exhaustive = current.role.bound;
3140
+ return _exhaustive;
3141
+ }
3142
+ }
3143
+ }
3144
+ function compareSemanticInclusivity(currentInclusive, previousInclusive) {
3145
+ if (currentInclusive === previousInclusive) {
3146
+ return 0;
3147
+ }
3148
+ return currentInclusive ? -1 : 1;
3149
+ }
3150
+ function customConstraintsContradict(lower, upper) {
3151
+ const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
3152
+ if (order > 0) {
3153
+ return true;
3154
+ }
3155
+ if (order < 0) {
3156
+ return false;
3157
+ }
3158
+ return !lower.role.inclusive || !upper.role.inclusive;
3159
+ }
3160
+ function describeCustomConstraintTag(constraint) {
3161
+ return constraint.provenance.tagName ?? constraint.constraintId;
3162
+ }
3163
+ function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
3164
+ if (ctx.extensionRegistry === void 0) {
3165
+ return;
3166
+ }
3167
+ const strongestByKey = /* @__PURE__ */ new Map();
3168
+ const lowerByFamily = /* @__PURE__ */ new Map();
3169
+ const upperByFamily = /* @__PURE__ */ new Map();
3170
+ for (const constraint of constraints) {
3171
+ if (constraint.constraintKind !== "custom") {
3172
+ continue;
3173
+ }
3174
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3175
+ if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
3176
+ continue;
3177
+ }
3178
+ const entry = {
3179
+ constraint,
3180
+ comparePayloads: registration.comparePayloads,
3181
+ role: registration.semanticRole
3182
+ };
3183
+ const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
3184
+ const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
3185
+ const previous = strongestByKey.get(boundKey);
3186
+ if (previous !== void 0) {
3187
+ const strength = compareCustomConstraintStrength(entry, previous);
3188
+ if (Number.isNaN(strength)) {
3189
+ addContradiction(
3190
+ ctx,
3191
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
3192
+ constraint.provenance,
3193
+ previous.constraint.provenance
3194
+ );
3195
+ continue;
3196
+ }
3197
+ if (strength < 0) {
3198
+ addConstraintBroadening(
3199
+ ctx,
3200
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
3201
+ constraint.provenance,
3202
+ previous.constraint.provenance
3203
+ );
3204
+ continue;
3205
+ }
3206
+ if (strength > 0) {
3207
+ strongestByKey.set(boundKey, entry);
3208
+ }
3209
+ } else {
3210
+ strongestByKey.set(boundKey, entry);
3211
+ }
3212
+ if (registration.semanticRole.bound === "lower") {
3213
+ lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3214
+ } else if (registration.semanticRole.bound === "upper") {
3215
+ upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3216
+ }
3217
+ }
3218
+ for (const [familyKey, lower] of lowerByFamily) {
3219
+ const upper = upperByFamily.get(familyKey);
3220
+ if (upper === void 0) {
3221
+ continue;
3222
+ }
3223
+ if (!customConstraintsContradict(lower, upper)) {
3224
+ continue;
3225
+ }
3226
+ addContradiction(
3227
+ ctx,
3228
+ `Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
3229
+ lower.constraint.provenance,
3230
+ upper.constraint.provenance
3231
+ );
3232
+ }
3233
+ }
3234
+ function checkNumericContradictions(ctx, fieldName, constraints) {
3235
+ const min = findNumeric(constraints, "minimum");
3236
+ const max = findNumeric(constraints, "maximum");
3237
+ const exMin = findNumeric(constraints, "exclusiveMinimum");
3238
+ const exMax = findNumeric(constraints, "exclusiveMaximum");
3239
+ if (min !== void 0 && max !== void 0 && min.value > max.value) {
3240
+ addContradiction(
3241
+ ctx,
3242
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
3243
+ min.provenance,
3244
+ max.provenance
3245
+ );
3246
+ }
3247
+ if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
3248
+ addContradiction(
3249
+ ctx,
3250
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
3251
+ exMin.provenance,
3252
+ max.provenance
3253
+ );
3254
+ }
3255
+ if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
3256
+ addContradiction(
3257
+ ctx,
3258
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3259
+ min.provenance,
3260
+ exMax.provenance
3261
+ );
3262
+ }
3263
+ if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
3264
+ addContradiction(
3265
+ ctx,
3266
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3267
+ exMin.provenance,
3268
+ exMax.provenance
3269
+ );
3270
+ }
3271
+ }
3272
+ function checkLengthContradictions(ctx, fieldName, constraints) {
3273
+ const minLen = findLength(constraints, "minLength");
3274
+ const maxLen = findLength(constraints, "maxLength");
3275
+ if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
3276
+ addContradiction(
3277
+ ctx,
3278
+ `Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
3279
+ minLen.provenance,
3280
+ maxLen.provenance
3281
+ );
3282
+ }
3283
+ const minItems = findLength(constraints, "minItems");
3284
+ const maxItems = findLength(constraints, "maxItems");
3285
+ if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
3286
+ addContradiction(
3287
+ ctx,
3288
+ `Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
3289
+ minItems.provenance,
3290
+ maxItems.provenance
3291
+ );
3292
+ }
3293
+ }
3294
+ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
3295
+ const members = findAllowedMembers(constraints);
3296
+ if (members.length < 2) return;
3297
+ const firstSet = new Set(members[0]?.members ?? []);
3298
+ for (let i = 1; i < members.length; i++) {
3299
+ const current = members[i];
3300
+ if (current === void 0) continue;
3301
+ for (const m of firstSet) {
3302
+ if (!current.members.includes(m)) {
3303
+ firstSet.delete(m);
3304
+ }
3305
+ }
3306
+ }
3307
+ if (firstSet.size === 0) {
3308
+ const first = members[0];
3309
+ const second = members[1];
3310
+ if (first !== void 0 && second !== void 0) {
3311
+ addContradiction(
3312
+ ctx,
3313
+ `Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
3314
+ first.provenance,
3315
+ second.provenance
3316
+ );
3317
+ }
3318
+ }
3319
+ }
3320
+ function checkConstContradictions(ctx, fieldName, constraints) {
3321
+ const constConstraints = findConstConstraints(constraints);
3322
+ if (constConstraints.length < 2) return;
3323
+ const first = constConstraints[0];
3324
+ if (first === void 0) return;
3325
+ for (let i = 1; i < constConstraints.length; i++) {
3326
+ const current = constConstraints[i];
3327
+ if (current === void 0) continue;
3328
+ if (jsonValueEquals(first.value, current.value)) {
3329
+ continue;
3330
+ }
3331
+ addContradiction(
3332
+ ctx,
3333
+ `Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
3334
+ first.provenance,
3335
+ current.provenance
3336
+ );
3337
+ }
3338
+ }
3339
+ function typeLabel(type) {
3340
+ switch (type.kind) {
3341
+ case "primitive":
3342
+ return type.primitiveKind;
3343
+ case "enum":
3344
+ return "enum";
3345
+ case "array":
3346
+ return "array";
3347
+ case "object":
3348
+ return "object";
3349
+ case "record":
3350
+ return "record";
3351
+ case "union":
3352
+ return "union";
3353
+ case "reference":
3354
+ return `reference(${type.name})`;
3355
+ case "dynamic":
3356
+ return `dynamic(${type.dynamicKind})`;
3357
+ case "custom":
3358
+ return `custom(${type.typeId})`;
3359
+ default: {
3360
+ const _exhaustive = type;
3361
+ return String(_exhaustive);
3362
+ }
3363
+ }
3364
+ }
3365
+ function dereferenceType(ctx, type) {
3366
+ let current = type;
3367
+ const seen = /* @__PURE__ */ new Set();
3368
+ while (current.kind === "reference") {
3369
+ if (seen.has(current.name)) {
3370
+ return current;
3371
+ }
3372
+ seen.add(current.name);
3373
+ const definition = ctx.typeRegistry[current.name];
3374
+ if (definition === void 0) {
3375
+ return current;
3376
+ }
3377
+ current = definition.type;
3378
+ }
3379
+ return current;
3380
+ }
3381
+ function collectReferencedTypeConstraints(ctx, type) {
3382
+ const collected = [];
3383
+ let current = type;
3384
+ const seen = /* @__PURE__ */ new Set();
3385
+ while (current.kind === "reference") {
3386
+ if (seen.has(current.name)) {
3387
+ break;
3388
+ }
3389
+ seen.add(current.name);
3390
+ const definition = ctx.typeRegistry[current.name];
3391
+ if (definition === void 0) {
3392
+ break;
3393
+ }
3394
+ if (definition.constraints !== void 0) {
3395
+ collected.push(...definition.constraints);
3396
+ }
3397
+ current = definition.type;
3398
+ }
3399
+ return collected;
3400
+ }
3401
+ function resolvePathTargetType(ctx, type, segments) {
3402
+ const effectiveType = dereferenceType(ctx, type);
3403
+ if (segments.length === 0) {
3404
+ return { kind: "resolved", type: effectiveType };
3405
+ }
3406
+ if (effectiveType.kind === "array") {
3407
+ return resolvePathTargetType(ctx, effectiveType.items, segments);
3408
+ }
3409
+ if (effectiveType.kind === "object") {
3410
+ const [segment, ...rest] = segments;
3411
+ if (segment === void 0) {
3412
+ throw new Error("Invariant violation: object path traversal requires a segment");
3413
+ }
3414
+ const property = effectiveType.properties.find((prop) => prop.name === segment);
3415
+ if (property === void 0) {
3416
+ return { kind: "missing-property", segment };
3417
+ }
3418
+ return resolvePathTargetType(ctx, property.type, rest);
3419
+ }
3420
+ return { kind: "unresolvable", type: effectiveType };
3421
+ }
3422
+ function isNullType(type) {
3423
+ return type.kind === "primitive" && type.primitiveKind === "null";
3424
+ }
3425
+ function collectCustomConstraintCandidateTypes(ctx, type) {
3426
+ const effectiveType = dereferenceType(ctx, type);
3427
+ const candidates = [effectiveType];
3428
+ if (effectiveType.kind === "array") {
3429
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
3430
+ }
3431
+ if (effectiveType.kind === "union") {
3432
+ const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
3433
+ const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
3434
+ if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
3435
+ const [nullableMember] = nonNullMembers;
3436
+ if (nullableMember !== void 0) {
3437
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
3438
+ }
3439
+ }
3440
+ }
3441
+ return candidates;
3442
+ }
3443
+ function formatPathTargetFieldName(fieldName, path3) {
3444
+ return path3.length === 0 ? fieldName : `${fieldName}.${path3.join(".")}`;
3445
+ }
3446
+ function checkConstraintOnType(ctx, fieldName, type, constraint) {
3447
+ const effectiveType = dereferenceType(ctx, type);
3448
+ const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
3449
+ const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
3450
+ const isArray = effectiveType.kind === "array";
3451
+ const isEnum = effectiveType.kind === "enum";
3452
+ const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
3453
+ const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
3454
+ const label = typeLabel(effectiveType);
3455
+ const ck = constraint.constraintKind;
3456
+ switch (ck) {
3457
+ case "minimum":
3458
+ case "maximum":
3459
+ case "exclusiveMinimum":
3460
+ case "exclusiveMaximum":
3461
+ case "multipleOf": {
3462
+ if (!isNumber) {
3463
+ addTypeMismatch(
3464
+ ctx,
3465
+ `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
3466
+ constraint.provenance
3467
+ );
3468
+ }
3469
+ break;
3470
+ }
3471
+ case "minLength":
3472
+ case "maxLength":
3473
+ case "pattern": {
3474
+ if (!isString && !isStringArray) {
3475
+ addTypeMismatch(
3476
+ ctx,
3477
+ `Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
3478
+ constraint.provenance
3479
+ );
3480
+ }
3481
+ break;
3482
+ }
3483
+ case "minItems":
3484
+ case "maxItems":
3485
+ case "uniqueItems": {
3486
+ if (!isArray) {
3487
+ addTypeMismatch(
3488
+ ctx,
3489
+ `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
3490
+ constraint.provenance
3491
+ );
3492
+ }
3493
+ break;
3494
+ }
3495
+ case "allowedMembers": {
3496
+ if (!isEnum) {
3497
+ addTypeMismatch(
3498
+ ctx,
3499
+ `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
3500
+ constraint.provenance
3501
+ );
3502
+ }
3503
+ break;
3504
+ }
3505
+ case "const": {
3506
+ const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
3507
+ effectiveType.primitiveKind
3508
+ ) || effectiveType.kind === "enum";
3509
+ if (!isPrimitiveConstType) {
3510
+ addTypeMismatch(
3511
+ ctx,
3512
+ `Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
3513
+ constraint.provenance
3514
+ );
3515
+ break;
3516
+ }
3517
+ if (effectiveType.kind === "primitive") {
3518
+ const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
3519
+ const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
3520
+ if (valueType !== expectedValueType) {
3521
+ addTypeMismatch(
3522
+ ctx,
3523
+ `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
3524
+ constraint.provenance
3525
+ );
3526
+ }
3527
+ break;
3528
+ }
3529
+ const memberValues = effectiveType.members.map((member) => member.value);
3530
+ if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
3531
+ addTypeMismatch(
3532
+ ctx,
3533
+ `Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
3534
+ constraint.provenance
3535
+ );
3536
+ }
3537
+ break;
3538
+ }
3539
+ case "custom": {
3540
+ checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
3541
+ break;
3542
+ }
3543
+ default: {
3544
+ const _exhaustive = constraint;
3545
+ throw new Error(
3546
+ `Unhandled constraint kind: ${_exhaustive.constraintKind}`
3547
+ );
3548
+ }
3549
+ }
3550
+ }
3551
+ function checkTypeApplicability(ctx, fieldName, type, constraints) {
3552
+ for (const constraint of constraints) {
3553
+ if (constraint.path) {
3554
+ const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
3555
+ const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
3556
+ if (resolution.kind === "missing-property") {
3557
+ addUnknownPathTarget(
3558
+ ctx,
3559
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
3560
+ constraint.provenance
3561
+ );
3562
+ continue;
3563
+ }
3564
+ if (resolution.kind === "unresolvable") {
3565
+ addTypeMismatch(
3566
+ ctx,
3567
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
3568
+ constraint.provenance
3569
+ );
3570
+ continue;
3571
+ }
3572
+ checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
3573
+ continue;
3574
+ }
3575
+ checkConstraintOnType(ctx, fieldName, type, constraint);
3576
+ }
3577
+ }
3578
+ function checkCustomConstraint(ctx, fieldName, type, constraint) {
3579
+ if (ctx.extensionRegistry === void 0) return;
3580
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3581
+ if (registration === void 0) {
3582
+ addUnknownExtension(
3583
+ ctx,
3584
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
3585
+ constraint.provenance
3586
+ );
3587
+ return;
3588
+ }
3589
+ const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
3590
+ const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
3591
+ if (normalizedTagName !== void 0) {
3592
+ const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
3593
+ const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
3594
+ if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
3595
+ (candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
3596
+ )) {
3597
+ addTypeMismatch(
3598
+ ctx,
3599
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3600
+ constraint.provenance
3601
+ );
3602
+ return;
3603
+ }
3604
+ }
3605
+ if (registration.applicableTypes === null) {
3606
+ if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
3607
+ addTypeMismatch(
3608
+ ctx,
3609
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3610
+ constraint.provenance
3611
+ );
3612
+ }
3613
+ return;
3614
+ }
3615
+ const applicableTypes = registration.applicableTypes;
3616
+ const matchesApplicableType = candidateTypes.some(
3617
+ (candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
3618
+ );
3619
+ if (!matchesApplicableType) {
3620
+ addTypeMismatch(
3621
+ ctx,
3622
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3623
+ constraint.provenance
3624
+ );
3625
+ }
3626
+ }
3627
+ function validateFieldNode(ctx, field) {
3628
+ validateConstraints(ctx, field.name, field.type, [
3629
+ ...collectReferencedTypeConstraints(ctx, field.type),
3630
+ ...field.constraints
3631
+ ]);
3632
+ if (field.type.kind === "object") {
3633
+ for (const prop of field.type.properties) {
3634
+ validateObjectProperty(ctx, field.name, prop);
3635
+ }
3636
+ }
3637
+ }
3638
+ function validateObjectProperty(ctx, parentName, prop) {
3639
+ const qualifiedName = `${parentName}.${prop.name}`;
3640
+ validateConstraints(ctx, qualifiedName, prop.type, [
3641
+ ...collectReferencedTypeConstraints(ctx, prop.type),
3642
+ ...prop.constraints
3643
+ ]);
3644
+ if (prop.type.kind === "object") {
3645
+ for (const nestedProp of prop.type.properties) {
3646
+ validateObjectProperty(ctx, qualifiedName, nestedProp);
3647
+ }
3648
+ }
3649
+ }
3650
+ function validateConstraints(ctx, name, type, constraints) {
3651
+ checkNumericContradictions(ctx, name, constraints);
3652
+ checkLengthContradictions(ctx, name, constraints);
3653
+ checkAllowedMembersContradiction(ctx, name, constraints);
3654
+ checkConstContradictions(ctx, name, constraints);
3655
+ checkConstraintBroadening(ctx, name, constraints);
3656
+ checkCustomConstraintSemantics(ctx, name, constraints);
3657
+ checkTypeApplicability(ctx, name, type, constraints);
3658
+ }
3659
+ function validateElement(ctx, element) {
3660
+ switch (element.kind) {
3661
+ case "field":
3662
+ validateFieldNode(ctx, element);
3663
+ break;
3664
+ case "group":
3665
+ for (const child of element.elements) {
3666
+ validateElement(ctx, child);
3667
+ }
3668
+ break;
3669
+ case "conditional":
3670
+ for (const child of element.elements) {
3671
+ validateElement(ctx, child);
3672
+ }
3673
+ break;
3674
+ default: {
3675
+ const _exhaustive = element;
3676
+ throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
3677
+ }
3678
+ }
3679
+ }
3680
+ function validateIR(ir, options) {
3681
+ const ctx = {
3682
+ diagnostics: [],
3683
+ extensionRegistry: options?.extensionRegistry,
3684
+ typeRegistry: ir.typeRegistry
3685
+ };
3686
+ for (const element of ir.elements) {
3687
+ validateElement(ctx, element);
3688
+ }
3689
+ return {
3690
+ diagnostics: ctx.diagnostics,
3691
+ valid: ctx.diagnostics.every((d) => d.severity !== "error")
3692
+ };
3693
+ }
3694
+
2344
3695
  // src/generators/class-schema.ts
2345
- function generateClassSchemas(analysis, source) {
3696
+ function generateClassSchemas(analysis, source, options) {
2346
3697
  const ir = canonicalizeTSDoc(analysis, source);
3698
+ const validationResult = validateIR(ir, {
3699
+ ...options?.extensionRegistry !== void 0 && {
3700
+ extensionRegistry: options.extensionRegistry
3701
+ },
3702
+ ...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
3703
+ });
3704
+ if (!validationResult.valid) {
3705
+ throw new Error(formatValidationError(validationResult.diagnostics));
3706
+ }
2347
3707
  return {
2348
- jsonSchema: generateJsonSchemaFromIR(ir),
3708
+ jsonSchema: generateJsonSchemaFromIR(ir, options),
2349
3709
  uiSchema: generateUiSchemaFromIR(ir)
2350
3710
  };
2351
3711
  }
3712
+ function formatValidationError(diagnostics) {
3713
+ const lines = diagnostics.map((diagnostic) => {
3714
+ const primary = formatLocation(diagnostic.primaryLocation);
3715
+ const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
3716
+ return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
3717
+ });
3718
+ return `FormSpec validation failed:
3719
+ ${lines.map((line) => `- ${line}`).join("\n")}`;
3720
+ }
3721
+ function formatLocation(location) {
3722
+ return `${location.file}:${String(location.line)}:${String(location.column)}`;
3723
+ }
2352
3724
  function generateSchemasFromClass(options) {
2353
3725
  const ctx = createProgramContext(options.filePath);
2354
3726
  const classDecl = findClassByName(ctx.sourceFile, options.className);
2355
3727
  if (!classDecl) {
2356
3728
  throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
2357
3729
  }
2358
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2359
- return generateClassSchemas(analysis, { file: options.filePath });
3730
+ const analysis = analyzeClassToIR(
3731
+ classDecl,
3732
+ ctx.checker,
3733
+ options.filePath,
3734
+ options.extensionRegistry
3735
+ );
3736
+ return generateClassSchemas(
3737
+ analysis,
3738
+ { file: options.filePath },
3739
+ {
3740
+ extensionRegistry: options.extensionRegistry,
3741
+ vendorPrefix: options.vendorPrefix
3742
+ }
3743
+ );
2360
3744
  }
2361
3745
  function generateSchemas(options) {
2362
- const ctx = createProgramContext(options.filePath);
2363
- const source = { file: options.filePath };
2364
- const classDecl = findClassByName(ctx.sourceFile, options.typeName);
2365
- if (classDecl) {
2366
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2367
- return generateClassSchemas(analysis, source);
2368
- }
2369
- const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
2370
- if (interfaceDecl) {
2371
- const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
2372
- return generateClassSchemas(analysis, source);
2373
- }
2374
- const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
2375
- if (typeAlias) {
2376
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
2377
- if (result.ok) {
2378
- return generateClassSchemas(result.analysis, source);
2379
- }
2380
- throw new Error(result.error);
2381
- }
2382
- throw new Error(
2383
- `Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
3746
+ const analysis = analyzeNamedTypeToIR(
3747
+ options.filePath,
3748
+ options.typeName,
3749
+ options.extensionRegistry
2384
3750
  );
3751
+ return generateClassSchemas(analysis, { file: options.filePath }, options);
2385
3752
  }
2386
3753
 
2387
3754
  // src/generators/mixed-authoring.ts
2388
3755
  function buildMixedAuthoringSchemas(options) {
2389
3756
  const { filePath, typeName, overlays, ...schemaOptions } = options;
2390
- const analysis = analyzeNamedType(filePath, typeName);
3757
+ const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
2391
3758
  const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2392
3759
  const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2393
3760
  return {
@@ -2395,29 +3762,6 @@ function buildMixedAuthoringSchemas(options) {
2395
3762
  uiSchema: generateUiSchemaFromIR(ir)
2396
3763
  };
2397
3764
  }
2398
- function analyzeNamedType(filePath, typeName) {
2399
- const ctx = createProgramContext(filePath);
2400
- const source = { file: filePath };
2401
- const classDecl = findClassByName(ctx.sourceFile, typeName);
2402
- if (classDecl !== null) {
2403
- return analyzeClassToIR(classDecl, ctx.checker, source.file);
2404
- }
2405
- const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2406
- if (interfaceDecl !== null) {
2407
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2408
- }
2409
- const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2410
- if (typeAlias !== null) {
2411
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2412
- if (result.ok) {
2413
- return result.analysis;
2414
- }
2415
- throw new Error(result.error);
2416
- }
2417
- throw new Error(
2418
- `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2419
- );
2420
- }
2421
3765
  function composeAnalysisWithOverlays(analysis, overlays) {
2422
3766
  const overlayIR = canonicalizeChainDSL(overlays);
2423
3767
  const overlayFields = collectOverlayFields(overlayIR.elements);
@@ -2487,7 +3831,7 @@ function assertSupportedOverlayField(baseField, overlayField) {
2487
3831
  `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2488
3832
  );
2489
3833
  }
2490
- if (overlayField.required) {
3834
+ if (overlayField.required && !baseField.required) {
2491
3835
  throw new Error(
2492
3836
  `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2493
3837
  );
@@ -2577,7 +3921,7 @@ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2577
3921
  const overlayOnly = overlayAnnotations.filter(
2578
3922
  (annotation) => !baseKeys.has(annotationKey(annotation))
2579
3923
  );
2580
- return [...overlayOnly, ...baseAnnotations];
3924
+ return [...baseAnnotations, ...overlayOnly];
2581
3925
  }
2582
3926
  function annotationKey(annotation) {
2583
3927
  return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;