@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/cli.cjs CHANGED
@@ -45,6 +45,7 @@ function canonicalizeChainDSL(form) {
45
45
  kind: "form-ir",
46
46
  irVersion: import_core.IR_VERSION,
47
47
  elements: canonicalizeElements(form.elements),
48
+ rootAnnotations: [],
48
49
  typeRegistry: {},
49
50
  provenance: CHAIN_DSL_PROVENANCE
50
51
  };
@@ -362,6 +363,7 @@ function canonicalizeTSDoc(analysis, source) {
362
363
  irVersion: import_core2.IR_VERSION,
363
364
  elements,
364
365
  typeRegistry: analysis.typeRegistry,
366
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
365
367
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
366
368
  provenance
367
369
  };
@@ -455,6 +457,9 @@ function generateJsonSchemaFromIR(ir, options) {
455
457
  const ctx = makeContext(options);
456
458
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
457
459
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
460
+ if (typeDef.constraints && typeDef.constraints.length > 0) {
461
+ applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
462
+ }
458
463
  if (typeDef.annotations && typeDef.annotations.length > 0) {
459
464
  applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
460
465
  }
@@ -623,7 +628,9 @@ function generateTypeNode(type, ctx) {
623
628
  }
624
629
  }
625
630
  function generatePrimitiveType(type) {
626
- return { type: type.primitiveKind };
631
+ return {
632
+ type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
633
+ };
627
634
  }
628
635
  function generateEnumType(type) {
629
636
  const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
@@ -796,7 +803,7 @@ function applyAnnotations(schema, annotations, ctx) {
796
803
  case "deprecated":
797
804
  schema.deprecated = true;
798
805
  if (annotation.message !== void 0 && annotation.message !== "") {
799
- schema["x-formspec-deprecation-description"] = annotation.message;
806
+ schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
800
807
  }
801
808
  break;
802
809
  case "placeholder":
@@ -829,7 +836,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
829
836
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
830
837
  );
831
838
  }
832
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
839
+ assignVendorPrefixedExtensionKeywords(
840
+ schema,
841
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
842
+ ctx.vendorPrefix,
843
+ `custom constraint "${constraint.constraintId}"`
844
+ );
833
845
  }
834
846
  function applyCustomAnnotation(schema, annotation, ctx) {
835
847
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -841,7 +853,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
841
853
  if (registration.toJsonSchema === void 0) {
842
854
  return;
843
855
  }
844
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
856
+ assignVendorPrefixedExtensionKeywords(
857
+ schema,
858
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
859
+ ctx.vendorPrefix,
860
+ `custom annotation "${annotation.annotationId}"`
861
+ );
862
+ }
863
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
864
+ for (const [key, value] of Object.entries(extensionSchema)) {
865
+ if (!key.startsWith(`${vendorPrefix}-`)) {
866
+ throw new Error(
867
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
868
+ );
869
+ }
870
+ schema[key] = value;
871
+ }
845
872
  }
846
873
  var init_ir_generator = __esm({
847
874
  "src/json-schema/ir-generator.ts"() {
@@ -1127,7 +1154,10 @@ var init_types = __esm({
1127
1154
  // src/extensions/registry.ts
1128
1155
  function createExtensionRegistry(extensions) {
1129
1156
  const typeMap = /* @__PURE__ */ new Map();
1157
+ const typeNameMap = /* @__PURE__ */ new Map();
1130
1158
  const constraintMap = /* @__PURE__ */ new Map();
1159
+ const constraintTagMap = /* @__PURE__ */ new Map();
1160
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
1131
1161
  const annotationMap = /* @__PURE__ */ new Map();
1132
1162
  for (const ext of extensions) {
1133
1163
  if (ext.types !== void 0) {
@@ -1137,6 +1167,27 @@ function createExtensionRegistry(extensions) {
1137
1167
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1138
1168
  }
1139
1169
  typeMap.set(qualifiedId, type);
1170
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
1171
+ if (typeNameMap.has(sourceTypeName)) {
1172
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
1173
+ }
1174
+ typeNameMap.set(sourceTypeName, {
1175
+ extensionId: ext.extensionId,
1176
+ registration: type
1177
+ });
1178
+ }
1179
+ if (type.builtinConstraintBroadenings !== void 0) {
1180
+ for (const broadening of type.builtinConstraintBroadenings) {
1181
+ const key = `${qualifiedId}:${broadening.tagName}`;
1182
+ if (builtinBroadeningMap.has(key)) {
1183
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
1184
+ }
1185
+ builtinBroadeningMap.set(key, {
1186
+ extensionId: ext.extensionId,
1187
+ registration: broadening
1188
+ });
1189
+ }
1190
+ }
1140
1191
  }
1141
1192
  }
1142
1193
  if (ext.constraints !== void 0) {
@@ -1148,6 +1199,17 @@ function createExtensionRegistry(extensions) {
1148
1199
  constraintMap.set(qualifiedId, constraint);
1149
1200
  }
1150
1201
  }
1202
+ if (ext.constraintTags !== void 0) {
1203
+ for (const tag of ext.constraintTags) {
1204
+ if (constraintTagMap.has(tag.tagName)) {
1205
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
1206
+ }
1207
+ constraintTagMap.set(tag.tagName, {
1208
+ extensionId: ext.extensionId,
1209
+ registration: tag
1210
+ });
1211
+ }
1212
+ }
1151
1213
  if (ext.annotations !== void 0) {
1152
1214
  for (const annotation of ext.annotations) {
1153
1215
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -1161,7 +1223,10 @@ function createExtensionRegistry(extensions) {
1161
1223
  return {
1162
1224
  extensions,
1163
1225
  findType: (typeId) => typeMap.get(typeId),
1226
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
1164
1227
  findConstraint: (constraintId) => constraintMap.get(constraintId),
1228
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
1229
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
1165
1230
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
1166
1231
  };
1167
1232
  }
@@ -1247,84 +1312,6 @@ var init_schema2 = __esm({
1247
1312
  }
1248
1313
  });
1249
1314
 
1250
- // src/analyzer/program.ts
1251
- function createProgramContext(filePath) {
1252
- const absolutePath = path.resolve(filePath);
1253
- const fileDir = path.dirname(absolutePath);
1254
- const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
1255
- let compilerOptions;
1256
- let fileNames;
1257
- if (configPath) {
1258
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
1259
- if (configFile.error) {
1260
- throw new Error(
1261
- `Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
1262
- );
1263
- }
1264
- const parsed = ts.parseJsonConfigFileContent(
1265
- configFile.config,
1266
- ts.sys,
1267
- path.dirname(configPath)
1268
- );
1269
- if (parsed.errors.length > 0) {
1270
- const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
1271
- throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
1272
- }
1273
- compilerOptions = parsed.options;
1274
- fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
1275
- } else {
1276
- compilerOptions = {
1277
- target: ts.ScriptTarget.ES2022,
1278
- module: ts.ModuleKind.NodeNext,
1279
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
1280
- strict: true,
1281
- skipLibCheck: true,
1282
- declaration: true
1283
- };
1284
- fileNames = [absolutePath];
1285
- }
1286
- const program = ts.createProgram(fileNames, compilerOptions);
1287
- const sourceFile = program.getSourceFile(absolutePath);
1288
- if (!sourceFile) {
1289
- throw new Error(`Could not find source file: ${absolutePath}`);
1290
- }
1291
- return {
1292
- program,
1293
- checker: program.getTypeChecker(),
1294
- sourceFile
1295
- };
1296
- }
1297
- function findNodeByName(sourceFile, name, predicate, getName) {
1298
- let result = null;
1299
- function visit(node) {
1300
- if (result) return;
1301
- if (predicate(node) && getName(node) === name) {
1302
- result = node;
1303
- return;
1304
- }
1305
- ts.forEachChild(node, visit);
1306
- }
1307
- visit(sourceFile);
1308
- return result;
1309
- }
1310
- function findClassByName(sourceFile, className) {
1311
- return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
1312
- }
1313
- function findInterfaceByName(sourceFile, interfaceName) {
1314
- return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
1315
- }
1316
- function findTypeAliasByName(sourceFile, aliasName) {
1317
- return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
1318
- }
1319
- var ts, path;
1320
- var init_program = __esm({
1321
- "src/analyzer/program.ts"() {
1322
- "use strict";
1323
- ts = __toESM(require("typescript"), 1);
1324
- path = __toESM(require("path"), 1);
1325
- }
1326
- });
1327
-
1328
1315
  // src/analyzer/json-utils.ts
1329
1316
  function tryParseJson(text) {
1330
1317
  try {
@@ -1340,7 +1327,7 @@ var init_json_utils = __esm({
1340
1327
  });
1341
1328
 
1342
1329
  // src/analyzer/tsdoc-parser.ts
1343
- function createFormSpecTSDocConfig() {
1330
+ function createFormSpecTSDocConfig(extensionTagNames = []) {
1344
1331
  const config = new import_tsdoc.TSDocConfiguration();
1345
1332
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
1346
1333
  config.addTagDefinition(
@@ -1360,13 +1347,33 @@ function createFormSpecTSDocConfig() {
1360
1347
  })
1361
1348
  );
1362
1349
  }
1350
+ for (const tagName of extensionTagNames) {
1351
+ config.addTagDefinition(
1352
+ new import_tsdoc.TSDocTagDefinition({
1353
+ tagName: "@" + tagName,
1354
+ syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
1355
+ allowMultiple: true
1356
+ })
1357
+ );
1358
+ }
1363
1359
  return config;
1364
1360
  }
1365
- function getParser() {
1366
- sharedParser ??= new import_tsdoc.TSDocParser(createFormSpecTSDocConfig());
1367
- return sharedParser;
1368
- }
1369
- function parseTSDocTags(node, file = "") {
1361
+ function getParser(options) {
1362
+ const extensionTagNames = [
1363
+ ...options?.extensionRegistry?.extensions.flatMap(
1364
+ (extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
1365
+ ) ?? []
1366
+ ].sort();
1367
+ const cacheKey = extensionTagNames.join("|");
1368
+ const existing = parserCache.get(cacheKey);
1369
+ if (existing) {
1370
+ return existing;
1371
+ }
1372
+ const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
1373
+ parserCache.set(cacheKey, parser);
1374
+ return parser;
1375
+ }
1376
+ function parseTSDocTags(node, file = "", options) {
1370
1377
  const constraints = [];
1371
1378
  const annotations = [];
1372
1379
  let displayName;
@@ -1377,17 +1384,17 @@ function parseTSDocTags(node, file = "") {
1377
1384
  let placeholderProvenance;
1378
1385
  const sourceFile = node.getSourceFile();
1379
1386
  const sourceText = sourceFile.getFullText();
1380
- const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
1387
+ const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
1381
1388
  if (commentRanges) {
1382
1389
  for (const range of commentRanges) {
1383
- if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
1390
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
1384
1391
  continue;
1385
1392
  }
1386
1393
  const commentText = sourceText.substring(range.pos, range.end);
1387
1394
  if (!commentText.startsWith("/**")) {
1388
1395
  continue;
1389
1396
  }
1390
- const parser = getParser();
1397
+ const parser = getParser(options);
1391
1398
  const parserContext = parser.parseRange(
1392
1399
  import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
1393
1400
  );
@@ -1398,26 +1405,31 @@ function parseTSDocTags(node, file = "") {
1398
1405
  const text2 = extractBlockText(block).trim();
1399
1406
  if (text2 === "") continue;
1400
1407
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1401
- if (tagName === "displayName") {
1402
- if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1403
- displayName = text2;
1404
- displayNameProvenance = provenance2;
1405
- }
1406
- } else if (tagName === "format") {
1407
- annotations.push({
1408
- kind: "annotation",
1409
- annotationKind: "format",
1410
- value: text2,
1411
- provenance: provenance2
1412
- });
1413
- } else {
1414
- if (tagName === "description" && description === void 0) {
1408
+ switch (tagName) {
1409
+ case "displayName":
1410
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1411
+ displayName = text2;
1412
+ displayNameProvenance = provenance2;
1413
+ }
1414
+ break;
1415
+ case "format":
1416
+ annotations.push({
1417
+ kind: "annotation",
1418
+ annotationKind: "format",
1419
+ value: text2,
1420
+ provenance: provenance2
1421
+ });
1422
+ break;
1423
+ case "description":
1415
1424
  description = text2;
1416
1425
  descriptionProvenance = provenance2;
1417
- } else if (tagName === "placeholder" && placeholder === void 0) {
1418
- placeholder = text2;
1419
- placeholderProvenance = provenance2;
1420
- }
1426
+ break;
1427
+ case "placeholder":
1428
+ if (placeholder === void 0) {
1429
+ placeholder = text2;
1430
+ placeholderProvenance = provenance2;
1431
+ }
1432
+ break;
1421
1433
  }
1422
1434
  continue;
1423
1435
  }
@@ -1426,7 +1438,7 @@ function parseTSDocTags(node, file = "") {
1426
1438
  const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1427
1439
  if (text === "" && expectedType !== "boolean") continue;
1428
1440
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1429
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1441
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1430
1442
  if (constraintNode) {
1431
1443
  constraints.push(constraintNode);
1432
1444
  }
@@ -1447,6 +1459,13 @@ function parseTSDocTags(node, file = "") {
1447
1459
  descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1448
1460
  }
1449
1461
  }
1462
+ if (description === void 0) {
1463
+ const summary = extractPlainText(docComment.summarySection).trim();
1464
+ if (summary !== "") {
1465
+ description = summary;
1466
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
1467
+ }
1468
+ }
1450
1469
  }
1451
1470
  }
1452
1471
  if (displayName !== void 0 && displayNameProvenance !== void 0) {
@@ -1473,7 +1492,7 @@ function parseTSDocTags(node, file = "") {
1473
1492
  provenance: placeholderProvenance
1474
1493
  });
1475
1494
  }
1476
- const jsDocTagsAll = ts2.getJSDocTags(node);
1495
+ const jsDocTagsAll = ts.getJSDocTags(node);
1477
1496
  for (const tag of jsDocTagsAll) {
1478
1497
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1479
1498
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
@@ -1486,7 +1505,7 @@ function parseTSDocTags(node, file = "") {
1486
1505
  annotations.push(defaultValueNode);
1487
1506
  continue;
1488
1507
  }
1489
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1508
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1490
1509
  if (constraintNode) {
1491
1510
  constraints.push(constraintNode);
1492
1511
  }
@@ -1496,7 +1515,7 @@ function parseTSDocTags(node, file = "") {
1496
1515
  function extractDisplayNameMetadata(node) {
1497
1516
  let displayName;
1498
1517
  const memberDisplayNames = /* @__PURE__ */ new Map();
1499
- for (const tag of ts2.getJSDocTags(node)) {
1518
+ for (const tag of ts.getJSDocTags(node)) {
1500
1519
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1501
1520
  if (tagName !== "displayName") continue;
1502
1521
  const commentText = getTagCommentText(tag);
@@ -1517,11 +1536,11 @@ function extractDisplayNameMetadata(node) {
1517
1536
  }
1518
1537
  function extractPathTarget(text) {
1519
1538
  const trimmed = text.trimStart();
1520
- const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
1521
- if (!match?.[1] || !match[2]) return null;
1539
+ const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
1540
+ if (!match?.[1]) return null;
1522
1541
  return {
1523
1542
  path: { segments: [match[1]] },
1524
- remainingText: match[2]
1543
+ remainingText: match[2] ?? ""
1525
1544
  };
1526
1545
  }
1527
1546
  function extractBlockText(block) {
@@ -1542,7 +1561,11 @@ function extractPlainText(node) {
1542
1561
  }
1543
1562
  return result;
1544
1563
  }
1545
- function parseConstraintValue(tagName, text, provenance) {
1564
+ function parseConstraintValue(tagName, text, provenance, options) {
1565
+ const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
1566
+ if (customConstraint) {
1567
+ return customConstraint;
1568
+ }
1546
1569
  if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1547
1570
  return null;
1548
1571
  }
@@ -1647,6 +1670,83 @@ function parseConstraintValue(tagName, text, provenance) {
1647
1670
  provenance
1648
1671
  };
1649
1672
  }
1673
+ function parseExtensionConstraintValue(tagName, text, provenance, options) {
1674
+ const pathResult = extractPathTarget(text);
1675
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1676
+ const path4 = pathResult?.path;
1677
+ const registry = options?.extensionRegistry;
1678
+ if (registry === void 0) {
1679
+ return null;
1680
+ }
1681
+ const directTag = registry.findConstraintTag(tagName);
1682
+ if (directTag !== void 0) {
1683
+ return makeCustomConstraintNode(
1684
+ directTag.extensionId,
1685
+ directTag.registration.constraintName,
1686
+ directTag.registration.parseValue(effectiveText),
1687
+ provenance,
1688
+ path4,
1689
+ registry
1690
+ );
1691
+ }
1692
+ if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1693
+ return null;
1694
+ }
1695
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
1696
+ if (broadenedTypeId === void 0) {
1697
+ return null;
1698
+ }
1699
+ const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
1700
+ if (broadened === void 0) {
1701
+ return null;
1702
+ }
1703
+ return makeCustomConstraintNode(
1704
+ broadened.extensionId,
1705
+ broadened.registration.constraintName,
1706
+ broadened.registration.parseValue(effectiveText),
1707
+ provenance,
1708
+ path4,
1709
+ registry
1710
+ );
1711
+ }
1712
+ function getBroadenedCustomTypeId(fieldType) {
1713
+ if (fieldType?.kind === "custom") {
1714
+ return fieldType.typeId;
1715
+ }
1716
+ if (fieldType?.kind !== "union") {
1717
+ return void 0;
1718
+ }
1719
+ const customMembers = fieldType.members.filter(
1720
+ (member) => member.kind === "custom"
1721
+ );
1722
+ if (customMembers.length !== 1) {
1723
+ return void 0;
1724
+ }
1725
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
1726
+ const allOtherMembersAreNull = nonCustomMembers.every(
1727
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
1728
+ );
1729
+ const customMember = customMembers[0];
1730
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
1731
+ }
1732
+ function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path4, registry) {
1733
+ const constraintId = `${extensionId}/${constraintName}`;
1734
+ const registration = registry.findConstraint(constraintId);
1735
+ if (registration === void 0) {
1736
+ throw new Error(
1737
+ `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
1738
+ );
1739
+ }
1740
+ return {
1741
+ kind: "constraint",
1742
+ constraintKind: "custom",
1743
+ constraintId,
1744
+ payload,
1745
+ compositionRule: registration.compositionRule,
1746
+ ...path4 && { path: path4 },
1747
+ provenance
1748
+ };
1749
+ }
1650
1750
  function parseDefaultValueValue(text, provenance) {
1651
1751
  const trimmed = text.trim();
1652
1752
  let value;
@@ -1703,13 +1803,13 @@ function getTagCommentText(tag) {
1703
1803
  if (typeof tag.comment === "string") {
1704
1804
  return tag.comment;
1705
1805
  }
1706
- return ts2.getTextOfJSDocComment(tag.comment);
1806
+ return ts.getTextOfJSDocComment(tag.comment);
1707
1807
  }
1708
- var ts2, import_tsdoc, import_core3, NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, sharedParser;
1808
+ var ts, import_tsdoc, import_core3, NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, parserCache;
1709
1809
  var init_tsdoc_parser = __esm({
1710
1810
  "src/analyzer/tsdoc-parser.ts"() {
1711
1811
  "use strict";
1712
- ts2 = __toESM(require("typescript"), 1);
1812
+ ts = __toESM(require("typescript"), 1);
1713
1813
  import_tsdoc = require("@microsoft/tsdoc");
1714
1814
  import_core3 = require("@formspec/core");
1715
1815
  init_json_utils();
@@ -1727,33 +1827,34 @@ var init_tsdoc_parser = __esm({
1727
1827
  maxItems: "maxItems"
1728
1828
  };
1729
1829
  TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1830
+ parserCache = /* @__PURE__ */ new Map();
1730
1831
  }
1731
1832
  });
1732
1833
 
1733
1834
  // src/analyzer/jsdoc-constraints.ts
1734
- function extractJSDocConstraintNodes(node, file = "") {
1735
- const result = parseTSDocTags(node, file);
1835
+ function extractJSDocConstraintNodes(node, file = "", options) {
1836
+ const result = parseTSDocTags(node, file, options);
1736
1837
  return [...result.constraints];
1737
1838
  }
1738
- function extractJSDocAnnotationNodes(node, file = "") {
1739
- const result = parseTSDocTags(node, file);
1839
+ function extractJSDocAnnotationNodes(node, file = "", options) {
1840
+ const result = parseTSDocTags(node, file, options);
1740
1841
  return [...result.annotations];
1741
1842
  }
1742
1843
  function extractDefaultValueAnnotation(initializer, file = "") {
1743
1844
  if (!initializer) return null;
1744
1845
  let value;
1745
- if (ts3.isStringLiteral(initializer)) {
1846
+ if (ts2.isStringLiteral(initializer)) {
1746
1847
  value = initializer.text;
1747
- } else if (ts3.isNumericLiteral(initializer)) {
1848
+ } else if (ts2.isNumericLiteral(initializer)) {
1748
1849
  value = Number(initializer.text);
1749
- } else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
1850
+ } else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
1750
1851
  value = true;
1751
- } else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
1852
+ } else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
1752
1853
  value = false;
1753
- } else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
1854
+ } else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
1754
1855
  value = null;
1755
- } else if (ts3.isPrefixUnaryExpression(initializer)) {
1756
- if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
1856
+ } else if (ts2.isPrefixUnaryExpression(initializer)) {
1857
+ if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
1757
1858
  value = -Number(initializer.operand.text);
1758
1859
  }
1759
1860
  }
@@ -1772,42 +1873,62 @@ function extractDefaultValueAnnotation(initializer, file = "") {
1772
1873
  }
1773
1874
  };
1774
1875
  }
1775
- var ts3;
1876
+ var ts2;
1776
1877
  var init_jsdoc_constraints = __esm({
1777
1878
  "src/analyzer/jsdoc-constraints.ts"() {
1778
1879
  "use strict";
1779
- ts3 = __toESM(require("typescript"), 1);
1880
+ ts2 = __toESM(require("typescript"), 1);
1780
1881
  init_tsdoc_parser();
1781
1882
  }
1782
1883
  });
1783
1884
 
1784
1885
  // src/analyzer/class-analyzer.ts
1785
1886
  function isObjectType(type) {
1786
- return !!(type.flags & ts4.TypeFlags.Object);
1887
+ return !!(type.flags & ts3.TypeFlags.Object);
1787
1888
  }
1788
1889
  function isTypeReference(type) {
1789
- return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1890
+ return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
1891
+ }
1892
+ function makeParseOptions(extensionRegistry, fieldType) {
1893
+ if (extensionRegistry === void 0 && fieldType === void 0) {
1894
+ return void 0;
1895
+ }
1896
+ return {
1897
+ ...extensionRegistry !== void 0 && { extensionRegistry },
1898
+ ...fieldType !== void 0 && { fieldType }
1899
+ };
1790
1900
  }
1791
- function analyzeClassToIR(classDecl, checker, file = "") {
1901
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1792
1902
  const name = classDecl.name?.text ?? "AnonymousClass";
1793
1903
  const fields = [];
1794
1904
  const fieldLayouts = [];
1795
1905
  const typeRegistry = {};
1796
- const annotations = extractJSDocAnnotationNodes(classDecl, file);
1906
+ const annotations = extractJSDocAnnotationNodes(
1907
+ classDecl,
1908
+ file,
1909
+ makeParseOptions(extensionRegistry)
1910
+ );
1797
1911
  const visiting = /* @__PURE__ */ new Set();
1798
1912
  const instanceMethods = [];
1799
1913
  const staticMethods = [];
1800
1914
  for (const member of classDecl.members) {
1801
- if (ts4.isPropertyDeclaration(member)) {
1802
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1915
+ if (ts3.isPropertyDeclaration(member)) {
1916
+ const fieldNode = analyzeFieldToIR(
1917
+ member,
1918
+ checker,
1919
+ file,
1920
+ typeRegistry,
1921
+ visiting,
1922
+ extensionRegistry
1923
+ );
1803
1924
  if (fieldNode) {
1804
1925
  fields.push(fieldNode);
1805
1926
  fieldLayouts.push({});
1806
1927
  }
1807
- } else if (ts4.isMethodDeclaration(member)) {
1928
+ } else if (ts3.isMethodDeclaration(member)) {
1808
1929
  const methodInfo = analyzeMethod(member, checker);
1809
1930
  if (methodInfo) {
1810
- const isStatic = member.modifiers?.some((m) => m.kind === ts4.SyntaxKind.StaticKeyword);
1931
+ const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
1811
1932
  if (isStatic) {
1812
1933
  staticMethods.push(methodInfo);
1813
1934
  } else {
@@ -1826,15 +1947,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1826
1947
  staticMethods
1827
1948
  };
1828
1949
  }
1829
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1950
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
1830
1951
  const name = interfaceDecl.name.text;
1831
1952
  const fields = [];
1832
1953
  const typeRegistry = {};
1833
- const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1954
+ const annotations = extractJSDocAnnotationNodes(
1955
+ interfaceDecl,
1956
+ file,
1957
+ makeParseOptions(extensionRegistry)
1958
+ );
1834
1959
  const visiting = /* @__PURE__ */ new Set();
1835
1960
  for (const member of interfaceDecl.members) {
1836
- if (ts4.isPropertySignature(member)) {
1837
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1961
+ if (ts3.isPropertySignature(member)) {
1962
+ const fieldNode = analyzeInterfacePropertyToIR(
1963
+ member,
1964
+ checker,
1965
+ file,
1966
+ typeRegistry,
1967
+ visiting,
1968
+ extensionRegistry
1969
+ );
1838
1970
  if (fieldNode) {
1839
1971
  fields.push(fieldNode);
1840
1972
  }
@@ -1851,11 +1983,11 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1851
1983
  staticMethods: []
1852
1984
  };
1853
1985
  }
1854
- function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1855
- if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1986
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1987
+ if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1856
1988
  const sourceFile = typeAlias.getSourceFile();
1857
1989
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
1858
- const kindDesc = ts4.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1990
+ const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1859
1991
  return {
1860
1992
  ok: false,
1861
1993
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
@@ -1864,11 +1996,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1864
1996
  const name = typeAlias.name.text;
1865
1997
  const fields = [];
1866
1998
  const typeRegistry = {};
1867
- const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1999
+ const annotations = extractJSDocAnnotationNodes(
2000
+ typeAlias,
2001
+ file,
2002
+ makeParseOptions(extensionRegistry)
2003
+ );
1868
2004
  const visiting = /* @__PURE__ */ new Set();
1869
2005
  for (const member of typeAlias.type.members) {
1870
- if (ts4.isPropertySignature(member)) {
1871
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2006
+ if (ts3.isPropertySignature(member)) {
2007
+ const fieldNode = analyzeInterfacePropertyToIR(
2008
+ member,
2009
+ checker,
2010
+ file,
2011
+ typeRegistry,
2012
+ visiting,
2013
+ extensionRegistry
2014
+ );
1872
2015
  if (fieldNode) {
1873
2016
  fields.push(fieldNode);
1874
2017
  }
@@ -1887,22 +2030,36 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1887
2030
  }
1888
2031
  };
1889
2032
  }
1890
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1891
- if (!ts4.isIdentifier(prop.name)) {
2033
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
2034
+ if (!ts3.isIdentifier(prop.name)) {
1892
2035
  return null;
1893
2036
  }
1894
2037
  const name = prop.name.text;
1895
2038
  const tsType = checker.getTypeAtLocation(prop);
1896
2039
  const optional = prop.questionToken !== void 0;
1897
2040
  const provenance = provenanceForNode(prop, file);
1898
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
2041
+ let type = resolveTypeNode(
2042
+ tsType,
2043
+ checker,
2044
+ file,
2045
+ typeRegistry,
2046
+ visiting,
2047
+ prop,
2048
+ extensionRegistry
2049
+ );
1899
2050
  const constraints = [];
1900
- if (prop.type) {
1901
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2051
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
2052
+ constraints.push(
2053
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2054
+ );
1902
2055
  }
1903
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2056
+ constraints.push(
2057
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
2058
+ );
1904
2059
  let annotations = [];
1905
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2060
+ annotations.push(
2061
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2062
+ );
1906
2063
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1907
2064
  if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1908
2065
  annotations.push(defaultAnnotation);
@@ -1918,22 +2075,36 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1918
2075
  provenance
1919
2076
  };
1920
2077
  }
1921
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
1922
- if (!ts4.isIdentifier(prop.name)) {
2078
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
2079
+ if (!ts3.isIdentifier(prop.name)) {
1923
2080
  return null;
1924
2081
  }
1925
2082
  const name = prop.name.text;
1926
2083
  const tsType = checker.getTypeAtLocation(prop);
1927
2084
  const optional = prop.questionToken !== void 0;
1928
2085
  const provenance = provenanceForNode(prop, file);
1929
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
2086
+ let type = resolveTypeNode(
2087
+ tsType,
2088
+ checker,
2089
+ file,
2090
+ typeRegistry,
2091
+ visiting,
2092
+ prop,
2093
+ extensionRegistry
2094
+ );
1930
2095
  const constraints = [];
1931
- if (prop.type) {
1932
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2096
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
2097
+ constraints.push(
2098
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2099
+ );
1933
2100
  }
1934
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2101
+ constraints.push(
2102
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
2103
+ );
1935
2104
  let annotations = [];
1936
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2105
+ annotations.push(
2106
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2107
+ );
1937
2108
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1938
2109
  return {
1939
2110
  kind: "field",
@@ -2007,20 +2178,94 @@ function parseEnumMemberDisplayName(value) {
2007
2178
  if (label === "") return null;
2008
2179
  return { value: match[1], label };
2009
2180
  }
2010
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
2011
- if (type.flags & ts4.TypeFlags.String) {
2181
+ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
2182
+ if (sourceNode === void 0 || extensionRegistry === void 0) {
2183
+ return null;
2184
+ }
2185
+ const typeNode = extractTypeNodeFromSource(sourceNode);
2186
+ if (typeNode === void 0) {
2187
+ return null;
2188
+ }
2189
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
2190
+ }
2191
+ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
2192
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2193
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
2194
+ }
2195
+ const typeName = getTypeNodeRegistrationName(typeNode);
2196
+ if (typeName === null) {
2197
+ return null;
2198
+ }
2199
+ const registration = extensionRegistry.findTypeByName(typeName);
2200
+ if (registration !== void 0) {
2201
+ return {
2202
+ kind: "custom",
2203
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
2204
+ payload: null
2205
+ };
2206
+ }
2207
+ if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
2208
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2209
+ if (aliasDecl !== void 0) {
2210
+ return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
2211
+ }
2212
+ }
2213
+ return null;
2214
+ }
2215
+ function extractTypeNodeFromSource(sourceNode) {
2216
+ if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
2217
+ return sourceNode.type;
2218
+ }
2219
+ if (ts3.isTypeNode(sourceNode)) {
2220
+ return sourceNode;
2221
+ }
2222
+ return void 0;
2223
+ }
2224
+ function getTypeNodeRegistrationName(typeNode) {
2225
+ if (ts3.isTypeReferenceNode(typeNode)) {
2226
+ return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
2227
+ }
2228
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2229
+ return getTypeNodeRegistrationName(typeNode.type);
2230
+ }
2231
+ if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
2232
+ return typeNode.getText();
2233
+ }
2234
+ return null;
2235
+ }
2236
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2237
+ const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2238
+ if (customType) {
2239
+ return customType;
2240
+ }
2241
+ const primitiveAlias = tryResolveNamedPrimitiveAlias(
2242
+ type,
2243
+ checker,
2244
+ file,
2245
+ typeRegistry,
2246
+ visiting,
2247
+ sourceNode,
2248
+ extensionRegistry
2249
+ );
2250
+ if (primitiveAlias) {
2251
+ return primitiveAlias;
2252
+ }
2253
+ if (type.flags & ts3.TypeFlags.String) {
2012
2254
  return { kind: "primitive", primitiveKind: "string" };
2013
2255
  }
2014
- if (type.flags & ts4.TypeFlags.Number) {
2256
+ if (type.flags & ts3.TypeFlags.Number) {
2015
2257
  return { kind: "primitive", primitiveKind: "number" };
2016
2258
  }
2017
- if (type.flags & ts4.TypeFlags.Boolean) {
2259
+ if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
2260
+ return { kind: "primitive", primitiveKind: "bigint" };
2261
+ }
2262
+ if (type.flags & ts3.TypeFlags.Boolean) {
2018
2263
  return { kind: "primitive", primitiveKind: "boolean" };
2019
2264
  }
2020
- if (type.flags & ts4.TypeFlags.Null) {
2265
+ if (type.flags & ts3.TypeFlags.Null) {
2021
2266
  return { kind: "primitive", primitiveKind: "null" };
2022
2267
  }
2023
- if (type.flags & ts4.TypeFlags.Undefined) {
2268
+ if (type.flags & ts3.TypeFlags.Undefined) {
2024
2269
  return { kind: "primitive", primitiveKind: "null" };
2025
2270
  }
2026
2271
  if (type.isStringLiteral()) {
@@ -2036,27 +2281,120 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2036
2281
  };
2037
2282
  }
2038
2283
  if (type.isUnion()) {
2039
- return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
2284
+ return resolveUnionType(
2285
+ type,
2286
+ checker,
2287
+ file,
2288
+ typeRegistry,
2289
+ visiting,
2290
+ sourceNode,
2291
+ extensionRegistry
2292
+ );
2040
2293
  }
2041
2294
  if (checker.isArrayType(type)) {
2042
- return resolveArrayType(type, checker, file, typeRegistry, visiting);
2295
+ return resolveArrayType(
2296
+ type,
2297
+ checker,
2298
+ file,
2299
+ typeRegistry,
2300
+ visiting,
2301
+ sourceNode,
2302
+ extensionRegistry
2303
+ );
2043
2304
  }
2044
2305
  if (isObjectType(type)) {
2045
- return resolveObjectType(type, checker, file, typeRegistry, visiting);
2306
+ return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
2046
2307
  }
2047
2308
  return { kind: "primitive", primitiveKind: "string" };
2048
2309
  }
2049
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
2310
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2311
+ if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2312
+ return null;
2313
+ }
2314
+ const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
2315
+ if (!aliasDecl) {
2316
+ return null;
2317
+ }
2318
+ const aliasName = aliasDecl.name.text;
2319
+ if (!typeRegistry[aliasName]) {
2320
+ const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
2321
+ const constraints = [
2322
+ ...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
2323
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
2324
+ ];
2325
+ const annotations = extractJSDocAnnotationNodes(
2326
+ aliasDecl,
2327
+ file,
2328
+ makeParseOptions(extensionRegistry)
2329
+ );
2330
+ typeRegistry[aliasName] = {
2331
+ name: aliasName,
2332
+ type: resolveAliasedPrimitiveTarget(
2333
+ aliasType,
2334
+ checker,
2335
+ file,
2336
+ typeRegistry,
2337
+ visiting,
2338
+ extensionRegistry
2339
+ ),
2340
+ ...constraints.length > 0 && { constraints },
2341
+ ...annotations.length > 0 && { annotations },
2342
+ provenance: provenanceForDeclaration(aliasDecl, file)
2343
+ };
2344
+ }
2345
+ return { kind: "reference", name: aliasName, typeArguments: [] };
2346
+ }
2347
+ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
2348
+ const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
2349
+ if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
2350
+ return void 0;
2351
+ }
2352
+ return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2353
+ }
2354
+ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2355
+ if (!ts3.isTypeReferenceNode(typeNode)) {
2356
+ return false;
2357
+ }
2358
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2359
+ if (!aliasDecl) {
2360
+ return false;
2361
+ }
2362
+ const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2363
+ return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2364
+ }
2365
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2366
+ const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2367
+ if (nestedAliasDecl !== void 0) {
2368
+ return resolveAliasedPrimitiveTarget(
2369
+ checker.getTypeFromTypeNode(nestedAliasDecl.type),
2370
+ checker,
2371
+ file,
2372
+ typeRegistry,
2373
+ visiting,
2374
+ extensionRegistry
2375
+ );
2376
+ }
2377
+ return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
2378
+ }
2379
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2050
2380
  const typeName = getNamedTypeName(type);
2051
2381
  const namedDecl = getNamedTypeDeclaration(type);
2052
2382
  if (typeName && typeName in typeRegistry) {
2053
2383
  return { kind: "reference", name: typeName, typeArguments: [] };
2054
2384
  }
2055
2385
  const allTypes = type.types;
2386
+ const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
2387
+ const nonNullSourceNodes = unionMemberTypeNodes.filter(
2388
+ (memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
2389
+ );
2056
2390
  const nonNullTypes = allTypes.filter(
2057
- (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
2391
+ (memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
2058
2392
  );
2059
- const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2393
+ const nonNullMembers = nonNullTypes.map((memberType, index) => ({
2394
+ memberType,
2395
+ sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
2396
+ }));
2397
+ const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
2060
2398
  const memberDisplayNames = /* @__PURE__ */ new Map();
2061
2399
  if (namedDecl) {
2062
2400
  for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
@@ -2072,7 +2410,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2072
2410
  if (!typeName) {
2073
2411
  return result;
2074
2412
  }
2075
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2413
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2076
2414
  typeRegistry[typeName] = {
2077
2415
  name: typeName,
2078
2416
  type: result,
@@ -2085,7 +2423,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2085
2423
  const displayName = memberDisplayNames.get(String(value));
2086
2424
  return displayName !== void 0 ? { value, displayName } : { value };
2087
2425
  });
2088
- const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
2426
+ const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
2089
2427
  if (isBooleanUnion2) {
2090
2428
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
2091
2429
  const result = hasNull ? {
@@ -2120,14 +2458,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2120
2458
  } : enumNode;
2121
2459
  return registerNamed(result);
2122
2460
  }
2123
- if (nonNullTypes.length === 1 && nonNullTypes[0]) {
2461
+ if (nonNullMembers.length === 1 && nonNullMembers[0]) {
2124
2462
  const inner = resolveTypeNode(
2125
- nonNullTypes[0],
2463
+ nonNullMembers[0].memberType,
2126
2464
  checker,
2127
2465
  file,
2128
2466
  typeRegistry,
2129
2467
  visiting,
2130
- sourceNode
2468
+ nonNullMembers[0].sourceNode ?? sourceNode,
2469
+ extensionRegistry
2131
2470
  );
2132
2471
  const result = hasNull ? {
2133
2472
  kind: "union",
@@ -2135,29 +2474,54 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2135
2474
  } : inner;
2136
2475
  return registerNamed(result);
2137
2476
  }
2138
- const members = nonNullTypes.map(
2139
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
2477
+ const members = nonNullMembers.map(
2478
+ ({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
2479
+ memberType,
2480
+ checker,
2481
+ file,
2482
+ typeRegistry,
2483
+ visiting,
2484
+ memberSourceNode ?? sourceNode,
2485
+ extensionRegistry
2486
+ )
2140
2487
  );
2141
2488
  if (hasNull) {
2142
2489
  members.push({ kind: "primitive", primitiveKind: "null" });
2143
2490
  }
2144
2491
  return registerNamed({ kind: "union", members });
2145
2492
  }
2146
- function resolveArrayType(type, checker, file, typeRegistry, visiting) {
2493
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2147
2494
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2148
2495
  const elementType = typeArgs?.[0];
2149
- const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
2496
+ const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
2497
+ const items = elementType ? resolveTypeNode(
2498
+ elementType,
2499
+ checker,
2500
+ file,
2501
+ typeRegistry,
2502
+ visiting,
2503
+ elementSourceNode,
2504
+ extensionRegistry
2505
+ ) : { kind: "primitive", primitiveKind: "string" };
2150
2506
  return { kind: "array", items };
2151
2507
  }
2152
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2508
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2153
2509
  if (type.getProperties().length > 0) {
2154
2510
  return null;
2155
2511
  }
2156
- const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
2512
+ const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
2157
2513
  if (!indexInfo) {
2158
2514
  return null;
2159
2515
  }
2160
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2516
+ const valueType = resolveTypeNode(
2517
+ indexInfo.type,
2518
+ checker,
2519
+ file,
2520
+ typeRegistry,
2521
+ visiting,
2522
+ void 0,
2523
+ extensionRegistry
2524
+ );
2161
2525
  return { kind: "record", valueType };
2162
2526
  }
2163
2527
  function typeNodeContainsReference(type, targetName) {
@@ -2185,7 +2549,7 @@ function typeNodeContainsReference(type, targetName) {
2185
2549
  }
2186
2550
  }
2187
2551
  }
2188
- function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2552
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2189
2553
  const typeName = getNamedTypeName(type);
2190
2554
  const namedTypeName = typeName ?? void 0;
2191
2555
  const namedDecl = getNamedTypeDeclaration(type);
@@ -2216,7 +2580,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2216
2580
  return { kind: "reference", name: namedTypeName, typeArguments: [] };
2217
2581
  }
2218
2582
  }
2219
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2583
+ const recordNode = tryResolveRecordType(
2584
+ type,
2585
+ checker,
2586
+ file,
2587
+ typeRegistry,
2588
+ visiting,
2589
+ extensionRegistry
2590
+ );
2220
2591
  if (recordNode) {
2221
2592
  visiting.delete(type);
2222
2593
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
@@ -2225,7 +2596,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2225
2596
  clearNamedTypeRegistration();
2226
2597
  return recordNode;
2227
2598
  }
2228
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2599
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2229
2600
  typeRegistry[namedTypeName] = {
2230
2601
  name: namedTypeName,
2231
2602
  type: recordNode,
@@ -2237,19 +2608,27 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2237
2608
  return recordNode;
2238
2609
  }
2239
2610
  const properties = [];
2240
- const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
2611
+ const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
2612
+ type,
2613
+ checker,
2614
+ file,
2615
+ typeRegistry,
2616
+ visiting,
2617
+ extensionRegistry
2618
+ );
2241
2619
  for (const prop of type.getProperties()) {
2242
2620
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2243
2621
  if (!declaration) continue;
2244
2622
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
2245
- const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
2623
+ const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
2246
2624
  const propTypeNode = resolveTypeNode(
2247
2625
  propType,
2248
2626
  checker,
2249
2627
  file,
2250
2628
  typeRegistry,
2251
2629
  visiting,
2252
- declaration
2630
+ declaration,
2631
+ extensionRegistry
2253
2632
  );
2254
2633
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2255
2634
  properties.push({
@@ -2268,7 +2647,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2268
2647
  additionalProperties: true
2269
2648
  };
2270
2649
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2271
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2650
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2272
2651
  typeRegistry[namedTypeName] = {
2273
2652
  name: namedTypeName,
2274
2653
  type: objectNode,
@@ -2279,19 +2658,26 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2279
2658
  }
2280
2659
  return objectNode;
2281
2660
  }
2282
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
2661
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2283
2662
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
2284
2663
  (s) => s?.declarations != null && s.declarations.length > 0
2285
2664
  );
2286
2665
  for (const symbol of symbols) {
2287
2666
  const declarations = symbol.declarations;
2288
2667
  if (!declarations) continue;
2289
- const classDecl = declarations.find(ts4.isClassDeclaration);
2668
+ const classDecl = declarations.find(ts3.isClassDeclaration);
2290
2669
  if (classDecl) {
2291
2670
  const map = /* @__PURE__ */ new Map();
2292
2671
  for (const member of classDecl.members) {
2293
- if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
2294
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
2672
+ if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
2673
+ const fieldNode = analyzeFieldToIR(
2674
+ member,
2675
+ checker,
2676
+ file,
2677
+ typeRegistry,
2678
+ visiting,
2679
+ extensionRegistry
2680
+ );
2295
2681
  if (fieldNode) {
2296
2682
  map.set(fieldNode.name, {
2297
2683
  constraints: [...fieldNode.constraints],
@@ -2303,28 +2689,86 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2303
2689
  }
2304
2690
  return map;
2305
2691
  }
2306
- const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
2692
+ const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
2307
2693
  if (interfaceDecl) {
2308
- return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
2694
+ return buildFieldNodeInfoMap(
2695
+ interfaceDecl.members,
2696
+ checker,
2697
+ file,
2698
+ typeRegistry,
2699
+ visiting,
2700
+ extensionRegistry
2701
+ );
2309
2702
  }
2310
- const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
2311
- if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
2703
+ const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
2704
+ if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
2312
2705
  return buildFieldNodeInfoMap(
2313
2706
  typeAliasDecl.type.members,
2314
2707
  checker,
2315
2708
  file,
2316
2709
  typeRegistry,
2317
- visiting
2710
+ visiting,
2711
+ extensionRegistry
2318
2712
  );
2319
2713
  }
2320
2714
  }
2321
2715
  return null;
2322
2716
  }
2323
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2717
+ function extractArrayElementTypeNode(sourceNode, checker) {
2718
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2719
+ if (typeNode === void 0) {
2720
+ return void 0;
2721
+ }
2722
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2723
+ if (ts3.isArrayTypeNode(resolvedTypeNode)) {
2724
+ return resolvedTypeNode.elementType;
2725
+ }
2726
+ if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
2727
+ return resolvedTypeNode.typeArguments[0];
2728
+ }
2729
+ return void 0;
2730
+ }
2731
+ function extractUnionMemberTypeNodes(sourceNode, checker) {
2732
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2733
+ if (!typeNode) {
2734
+ return [];
2735
+ }
2736
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2737
+ return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
2738
+ }
2739
+ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
2740
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2741
+ return resolveAliasedTypeNode(typeNode.type, checker, visited);
2742
+ }
2743
+ if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
2744
+ return typeNode;
2745
+ }
2746
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2747
+ const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2748
+ if (aliasDecl === void 0 || visited.has(aliasDecl)) {
2749
+ return typeNode;
2750
+ }
2751
+ visited.add(aliasDecl);
2752
+ return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
2753
+ }
2754
+ function isNullishTypeNode(typeNode) {
2755
+ if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
2756
+ return true;
2757
+ }
2758
+ return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
2759
+ }
2760
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
2324
2761
  const map = /* @__PURE__ */ new Map();
2325
2762
  for (const member of members) {
2326
- if (ts4.isPropertySignature(member)) {
2327
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2763
+ if (ts3.isPropertySignature(member)) {
2764
+ const fieldNode = analyzeInterfacePropertyToIR(
2765
+ member,
2766
+ checker,
2767
+ file,
2768
+ typeRegistry,
2769
+ visiting,
2770
+ extensionRegistry
2771
+ );
2328
2772
  if (fieldNode) {
2329
2773
  map.set(fieldNode.name, {
2330
2774
  constraints: [...fieldNode.constraints],
@@ -2336,8 +2780,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2336
2780
  }
2337
2781
  return map;
2338
2782
  }
2339
- function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2340
- if (!ts4.isTypeReferenceNode(typeNode)) return [];
2783
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
2784
+ if (!ts3.isTypeReferenceNode(typeNode)) return [];
2341
2785
  if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
2342
2786
  const aliasName = typeNode.typeName.getText();
2343
2787
  throw new Error(
@@ -2346,11 +2790,26 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2346
2790
  }
2347
2791
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2348
2792
  if (!symbol?.declarations) return [];
2349
- const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
2793
+ const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
2350
2794
  if (!aliasDecl) return [];
2351
- if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
2352
- const constraints = extractJSDocConstraintNodes(aliasDecl, file);
2353
- constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
2795
+ if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
2796
+ const aliasFieldType = resolveTypeNode(
2797
+ checker.getTypeAtLocation(aliasDecl.type),
2798
+ checker,
2799
+ file,
2800
+ {},
2801
+ /* @__PURE__ */ new Set(),
2802
+ aliasDecl.type,
2803
+ extensionRegistry
2804
+ );
2805
+ const constraints = extractJSDocConstraintNodes(
2806
+ aliasDecl,
2807
+ file,
2808
+ makeParseOptions(extensionRegistry, aliasFieldType)
2809
+ );
2810
+ constraints.push(
2811
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
2812
+ );
2354
2813
  return constraints;
2355
2814
  }
2356
2815
  function provenanceForNode(node, file) {
@@ -2376,14 +2835,14 @@ function getNamedTypeName(type) {
2376
2835
  const symbol = type.getSymbol();
2377
2836
  if (symbol?.declarations) {
2378
2837
  const decl = symbol.declarations[0];
2379
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2380
- const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
2838
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
2839
+ const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
2381
2840
  if (name) return name;
2382
2841
  }
2383
2842
  }
2384
2843
  const aliasSymbol = type.aliasSymbol;
2385
2844
  if (aliasSymbol?.declarations) {
2386
- const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2845
+ const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
2387
2846
  if (aliasDecl) {
2388
2847
  return aliasDecl.name.text;
2389
2848
  }
@@ -2394,24 +2853,24 @@ function getNamedTypeDeclaration(type) {
2394
2853
  const symbol = type.getSymbol();
2395
2854
  if (symbol?.declarations) {
2396
2855
  const decl = symbol.declarations[0];
2397
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2856
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
2398
2857
  return decl;
2399
2858
  }
2400
2859
  }
2401
2860
  const aliasSymbol = type.aliasSymbol;
2402
2861
  if (aliasSymbol?.declarations) {
2403
- return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2862
+ return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
2404
2863
  }
2405
2864
  return void 0;
2406
2865
  }
2407
2866
  function analyzeMethod(method, checker) {
2408
- if (!ts4.isIdentifier(method.name)) {
2867
+ if (!ts3.isIdentifier(method.name)) {
2409
2868
  return null;
2410
2869
  }
2411
2870
  const name = method.name.text;
2412
2871
  const parameters = [];
2413
2872
  for (const param of method.parameters) {
2414
- if (ts4.isIdentifier(param.name)) {
2873
+ if (ts3.isIdentifier(param.name)) {
2415
2874
  const paramInfo = analyzeParameter(param, checker);
2416
2875
  parameters.push(paramInfo);
2417
2876
  }
@@ -2422,7 +2881,7 @@ function analyzeMethod(method, checker) {
2422
2881
  return { name, parameters, returnTypeNode, returnType };
2423
2882
  }
2424
2883
  function analyzeParameter(param, checker) {
2425
- const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
2884
+ const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
2426
2885
  const typeNode = param.type;
2427
2886
  const type = checker.getTypeAtLocation(param);
2428
2887
  const formSpecExportName = detectFormSpecReference(typeNode);
@@ -2431,24 +2890,24 @@ function analyzeParameter(param, checker) {
2431
2890
  }
2432
2891
  function detectFormSpecReference(typeNode) {
2433
2892
  if (!typeNode) return null;
2434
- if (!ts4.isTypeReferenceNode(typeNode)) return null;
2435
- const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
2893
+ if (!ts3.isTypeReferenceNode(typeNode)) return null;
2894
+ const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
2436
2895
  if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
2437
2896
  const typeArg = typeNode.typeArguments?.[0];
2438
- if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
2439
- if (ts4.isIdentifier(typeArg.exprName)) {
2897
+ if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
2898
+ if (ts3.isIdentifier(typeArg.exprName)) {
2440
2899
  return typeArg.exprName.text;
2441
2900
  }
2442
- if (ts4.isQualifiedName(typeArg.exprName)) {
2901
+ if (ts3.isQualifiedName(typeArg.exprName)) {
2443
2902
  return typeArg.exprName.right.text;
2444
2903
  }
2445
2904
  return null;
2446
2905
  }
2447
- var ts4, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
2906
+ var ts3, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
2448
2907
  var init_class_analyzer = __esm({
2449
2908
  "src/analyzer/class-analyzer.ts"() {
2450
2909
  "use strict";
2451
- ts4 = __toESM(require("typescript"), 1);
2910
+ ts3 = __toESM(require("typescript"), 1);
2452
2911
  init_jsdoc_constraints();
2453
2912
  init_tsdoc_parser();
2454
2913
  RESOLVING_TYPE_PLACEHOLDER = {
@@ -2460,47 +2919,969 @@ var init_class_analyzer = __esm({
2460
2919
  }
2461
2920
  });
2462
2921
 
2922
+ // src/analyzer/program.ts
2923
+ function createProgramContext(filePath) {
2924
+ const absolutePath = path.resolve(filePath);
2925
+ const fileDir = path.dirname(absolutePath);
2926
+ const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
2927
+ let compilerOptions;
2928
+ let fileNames;
2929
+ if (configPath) {
2930
+ const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
2931
+ if (configFile.error) {
2932
+ throw new Error(
2933
+ `Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
2934
+ );
2935
+ }
2936
+ const parsed = ts4.parseJsonConfigFileContent(
2937
+ configFile.config,
2938
+ ts4.sys,
2939
+ path.dirname(configPath)
2940
+ );
2941
+ if (parsed.errors.length > 0) {
2942
+ const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
2943
+ throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
2944
+ }
2945
+ compilerOptions = parsed.options;
2946
+ fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
2947
+ } else {
2948
+ compilerOptions = {
2949
+ target: ts4.ScriptTarget.ES2022,
2950
+ module: ts4.ModuleKind.NodeNext,
2951
+ moduleResolution: ts4.ModuleResolutionKind.NodeNext,
2952
+ strict: true,
2953
+ skipLibCheck: true,
2954
+ declaration: true
2955
+ };
2956
+ fileNames = [absolutePath];
2957
+ }
2958
+ const program = ts4.createProgram(fileNames, compilerOptions);
2959
+ const sourceFile = program.getSourceFile(absolutePath);
2960
+ if (!sourceFile) {
2961
+ throw new Error(`Could not find source file: ${absolutePath}`);
2962
+ }
2963
+ return {
2964
+ program,
2965
+ checker: program.getTypeChecker(),
2966
+ sourceFile
2967
+ };
2968
+ }
2969
+ function findNodeByName(sourceFile, name, predicate, getName) {
2970
+ let result = null;
2971
+ function visit(node) {
2972
+ if (result) return;
2973
+ if (predicate(node) && getName(node) === name) {
2974
+ result = node;
2975
+ return;
2976
+ }
2977
+ ts4.forEachChild(node, visit);
2978
+ }
2979
+ visit(sourceFile);
2980
+ return result;
2981
+ }
2982
+ function findClassByName(sourceFile, className) {
2983
+ return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
2984
+ }
2985
+ function findInterfaceByName(sourceFile, interfaceName) {
2986
+ return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
2987
+ }
2988
+ function findTypeAliasByName(sourceFile, aliasName) {
2989
+ return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
2990
+ }
2991
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
2992
+ const ctx = createProgramContext(filePath);
2993
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2994
+ if (classDecl !== null) {
2995
+ return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
2996
+ }
2997
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2998
+ if (interfaceDecl !== null) {
2999
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
3000
+ }
3001
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3002
+ if (typeAlias !== null) {
3003
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
3004
+ if (result.ok) {
3005
+ return result.analysis;
3006
+ }
3007
+ throw new Error(result.error);
3008
+ }
3009
+ throw new Error(
3010
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
3011
+ );
3012
+ }
3013
+ var ts4, path;
3014
+ var init_program = __esm({
3015
+ "src/analyzer/program.ts"() {
3016
+ "use strict";
3017
+ ts4 = __toESM(require("typescript"), 1);
3018
+ path = __toESM(require("path"), 1);
3019
+ init_class_analyzer();
3020
+ }
3021
+ });
3022
+
3023
+ // src/validate/constraint-validator.ts
3024
+ function addContradiction(ctx, message, primary, related) {
3025
+ ctx.diagnostics.push({
3026
+ code: "CONTRADICTING_CONSTRAINTS",
3027
+ message,
3028
+ severity: "error",
3029
+ primaryLocation: primary,
3030
+ relatedLocations: [related]
3031
+ });
3032
+ }
3033
+ function addTypeMismatch(ctx, message, primary) {
3034
+ ctx.diagnostics.push({
3035
+ code: "TYPE_MISMATCH",
3036
+ message,
3037
+ severity: "error",
3038
+ primaryLocation: primary,
3039
+ relatedLocations: []
3040
+ });
3041
+ }
3042
+ function addUnknownExtension(ctx, message, primary) {
3043
+ ctx.diagnostics.push({
3044
+ code: "UNKNOWN_EXTENSION",
3045
+ message,
3046
+ severity: "warning",
3047
+ primaryLocation: primary,
3048
+ relatedLocations: []
3049
+ });
3050
+ }
3051
+ function addUnknownPathTarget(ctx, message, primary) {
3052
+ ctx.diagnostics.push({
3053
+ code: "UNKNOWN_PATH_TARGET",
3054
+ message,
3055
+ severity: "error",
3056
+ primaryLocation: primary,
3057
+ relatedLocations: []
3058
+ });
3059
+ }
3060
+ function addConstraintBroadening(ctx, message, primary, related) {
3061
+ ctx.diagnostics.push({
3062
+ code: "CONSTRAINT_BROADENING",
3063
+ message,
3064
+ severity: "error",
3065
+ primaryLocation: primary,
3066
+ relatedLocations: [related]
3067
+ });
3068
+ }
3069
+ function getExtensionIdFromConstraintId(constraintId) {
3070
+ const separator = constraintId.lastIndexOf("/");
3071
+ if (separator <= 0) {
3072
+ return null;
3073
+ }
3074
+ return constraintId.slice(0, separator);
3075
+ }
3076
+ function findNumeric(constraints, constraintKind) {
3077
+ return constraints.find((c) => c.constraintKind === constraintKind);
3078
+ }
3079
+ function findLength(constraints, constraintKind) {
3080
+ return constraints.find((c) => c.constraintKind === constraintKind);
3081
+ }
3082
+ function findAllowedMembers(constraints) {
3083
+ return constraints.filter(
3084
+ (c) => c.constraintKind === "allowedMembers"
3085
+ );
3086
+ }
3087
+ function findConstConstraints(constraints) {
3088
+ return constraints.filter(
3089
+ (c) => c.constraintKind === "const"
3090
+ );
3091
+ }
3092
+ function jsonValueEquals(left, right) {
3093
+ if (left === right) {
3094
+ return true;
3095
+ }
3096
+ if (Array.isArray(left) || Array.isArray(right)) {
3097
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
3098
+ return false;
3099
+ }
3100
+ return left.every((item, index) => jsonValueEquals(item, right[index]));
3101
+ }
3102
+ if (isJsonObject(left) || isJsonObject(right)) {
3103
+ if (!isJsonObject(left) || !isJsonObject(right)) {
3104
+ return false;
3105
+ }
3106
+ const leftKeys = Object.keys(left).sort();
3107
+ const rightKeys = Object.keys(right).sort();
3108
+ if (leftKeys.length !== rightKeys.length) {
3109
+ return false;
3110
+ }
3111
+ return leftKeys.every((key, index) => {
3112
+ const rightKey = rightKeys[index];
3113
+ if (rightKey !== key) {
3114
+ return false;
3115
+ }
3116
+ const leftValue = left[key];
3117
+ const rightValue = right[rightKey];
3118
+ return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
3119
+ });
3120
+ }
3121
+ return false;
3122
+ }
3123
+ function isJsonObject(value) {
3124
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3125
+ }
3126
+ function isOrderedBoundConstraint(constraint) {
3127
+ 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";
3128
+ }
3129
+ function pathKey(constraint) {
3130
+ return constraint.path?.segments.join(".") ?? "";
3131
+ }
3132
+ function orderedBoundFamily(kind) {
3133
+ switch (kind) {
3134
+ case "minimum":
3135
+ case "exclusiveMinimum":
3136
+ return "numeric-lower";
3137
+ case "maximum":
3138
+ case "exclusiveMaximum":
3139
+ return "numeric-upper";
3140
+ case "minLength":
3141
+ return "minLength";
3142
+ case "minItems":
3143
+ return "minItems";
3144
+ case "maxLength":
3145
+ return "maxLength";
3146
+ case "maxItems":
3147
+ return "maxItems";
3148
+ default: {
3149
+ const _exhaustive = kind;
3150
+ return _exhaustive;
3151
+ }
3152
+ }
3153
+ }
3154
+ function isNumericLowerKind(kind) {
3155
+ return kind === "minimum" || kind === "exclusiveMinimum";
3156
+ }
3157
+ function isNumericUpperKind(kind) {
3158
+ return kind === "maximum" || kind === "exclusiveMaximum";
3159
+ }
3160
+ function describeConstraintTag(constraint) {
3161
+ return `@${constraint.constraintKind}`;
3162
+ }
3163
+ function compareConstraintStrength(current, previous) {
3164
+ const family = orderedBoundFamily(current.constraintKind);
3165
+ if (family === "numeric-lower") {
3166
+ if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
3167
+ throw new Error("numeric-lower family received non-numeric lower-bound constraint");
3168
+ }
3169
+ if (current.value !== previous.value) {
3170
+ return current.value > previous.value ? 1 : -1;
3171
+ }
3172
+ if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
3173
+ return 1;
3174
+ }
3175
+ if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
3176
+ return -1;
3177
+ }
3178
+ return 0;
3179
+ }
3180
+ if (family === "numeric-upper") {
3181
+ if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
3182
+ throw new Error("numeric-upper family received non-numeric upper-bound constraint");
3183
+ }
3184
+ if (current.value !== previous.value) {
3185
+ return current.value < previous.value ? 1 : -1;
3186
+ }
3187
+ if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
3188
+ return 1;
3189
+ }
3190
+ if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
3191
+ return -1;
3192
+ }
3193
+ return 0;
3194
+ }
3195
+ switch (family) {
3196
+ case "minLength":
3197
+ case "minItems":
3198
+ if (current.value === previous.value) {
3199
+ return 0;
3200
+ }
3201
+ return current.value > previous.value ? 1 : -1;
3202
+ case "maxLength":
3203
+ case "maxItems":
3204
+ if (current.value === previous.value) {
3205
+ return 0;
3206
+ }
3207
+ return current.value < previous.value ? 1 : -1;
3208
+ default: {
3209
+ const _exhaustive = family;
3210
+ return _exhaustive;
3211
+ }
3212
+ }
3213
+ }
3214
+ function checkConstraintBroadening(ctx, fieldName, constraints) {
3215
+ const strongestByKey = /* @__PURE__ */ new Map();
3216
+ for (const constraint of constraints) {
3217
+ if (!isOrderedBoundConstraint(constraint)) {
3218
+ continue;
3219
+ }
3220
+ const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
3221
+ const previous = strongestByKey.get(key);
3222
+ if (previous === void 0) {
3223
+ strongestByKey.set(key, constraint);
3224
+ continue;
3225
+ }
3226
+ const strength = compareConstraintStrength(constraint, previous);
3227
+ if (strength < 0) {
3228
+ const displayFieldName = formatPathTargetFieldName(
3229
+ fieldName,
3230
+ constraint.path?.segments ?? []
3231
+ );
3232
+ addConstraintBroadening(
3233
+ ctx,
3234
+ `Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
3235
+ constraint.provenance,
3236
+ previous.provenance
3237
+ );
3238
+ continue;
3239
+ }
3240
+ if (strength <= 0) {
3241
+ continue;
3242
+ }
3243
+ strongestByKey.set(key, constraint);
3244
+ }
3245
+ }
3246
+ function compareCustomConstraintStrength(current, previous) {
3247
+ const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
3248
+ const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
3249
+ switch (current.role.bound) {
3250
+ case "lower":
3251
+ return equalPayloadTiebreaker;
3252
+ case "upper":
3253
+ return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
3254
+ case "exact":
3255
+ return order === 0 ? 0 : Number.NaN;
3256
+ default: {
3257
+ const _exhaustive = current.role.bound;
3258
+ return _exhaustive;
3259
+ }
3260
+ }
3261
+ }
3262
+ function compareSemanticInclusivity(currentInclusive, previousInclusive) {
3263
+ if (currentInclusive === previousInclusive) {
3264
+ return 0;
3265
+ }
3266
+ return currentInclusive ? -1 : 1;
3267
+ }
3268
+ function customConstraintsContradict(lower, upper) {
3269
+ const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
3270
+ if (order > 0) {
3271
+ return true;
3272
+ }
3273
+ if (order < 0) {
3274
+ return false;
3275
+ }
3276
+ return !lower.role.inclusive || !upper.role.inclusive;
3277
+ }
3278
+ function describeCustomConstraintTag(constraint) {
3279
+ return constraint.provenance.tagName ?? constraint.constraintId;
3280
+ }
3281
+ function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
3282
+ if (ctx.extensionRegistry === void 0) {
3283
+ return;
3284
+ }
3285
+ const strongestByKey = /* @__PURE__ */ new Map();
3286
+ const lowerByFamily = /* @__PURE__ */ new Map();
3287
+ const upperByFamily = /* @__PURE__ */ new Map();
3288
+ for (const constraint of constraints) {
3289
+ if (constraint.constraintKind !== "custom") {
3290
+ continue;
3291
+ }
3292
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3293
+ if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
3294
+ continue;
3295
+ }
3296
+ const entry = {
3297
+ constraint,
3298
+ comparePayloads: registration.comparePayloads,
3299
+ role: registration.semanticRole
3300
+ };
3301
+ const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
3302
+ const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
3303
+ const previous = strongestByKey.get(boundKey);
3304
+ if (previous !== void 0) {
3305
+ const strength = compareCustomConstraintStrength(entry, previous);
3306
+ if (Number.isNaN(strength)) {
3307
+ addContradiction(
3308
+ ctx,
3309
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
3310
+ constraint.provenance,
3311
+ previous.constraint.provenance
3312
+ );
3313
+ continue;
3314
+ }
3315
+ if (strength < 0) {
3316
+ addConstraintBroadening(
3317
+ ctx,
3318
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
3319
+ constraint.provenance,
3320
+ previous.constraint.provenance
3321
+ );
3322
+ continue;
3323
+ }
3324
+ if (strength > 0) {
3325
+ strongestByKey.set(boundKey, entry);
3326
+ }
3327
+ } else {
3328
+ strongestByKey.set(boundKey, entry);
3329
+ }
3330
+ if (registration.semanticRole.bound === "lower") {
3331
+ lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3332
+ } else if (registration.semanticRole.bound === "upper") {
3333
+ upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3334
+ }
3335
+ }
3336
+ for (const [familyKey, lower] of lowerByFamily) {
3337
+ const upper = upperByFamily.get(familyKey);
3338
+ if (upper === void 0) {
3339
+ continue;
3340
+ }
3341
+ if (!customConstraintsContradict(lower, upper)) {
3342
+ continue;
3343
+ }
3344
+ addContradiction(
3345
+ ctx,
3346
+ `Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
3347
+ lower.constraint.provenance,
3348
+ upper.constraint.provenance
3349
+ );
3350
+ }
3351
+ }
3352
+ function checkNumericContradictions(ctx, fieldName, constraints) {
3353
+ const min = findNumeric(constraints, "minimum");
3354
+ const max = findNumeric(constraints, "maximum");
3355
+ const exMin = findNumeric(constraints, "exclusiveMinimum");
3356
+ const exMax = findNumeric(constraints, "exclusiveMaximum");
3357
+ if (min !== void 0 && max !== void 0 && min.value > max.value) {
3358
+ addContradiction(
3359
+ ctx,
3360
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
3361
+ min.provenance,
3362
+ max.provenance
3363
+ );
3364
+ }
3365
+ if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
3366
+ addContradiction(
3367
+ ctx,
3368
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
3369
+ exMin.provenance,
3370
+ max.provenance
3371
+ );
3372
+ }
3373
+ if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
3374
+ addContradiction(
3375
+ ctx,
3376
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3377
+ min.provenance,
3378
+ exMax.provenance
3379
+ );
3380
+ }
3381
+ if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
3382
+ addContradiction(
3383
+ ctx,
3384
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3385
+ exMin.provenance,
3386
+ exMax.provenance
3387
+ );
3388
+ }
3389
+ }
3390
+ function checkLengthContradictions(ctx, fieldName, constraints) {
3391
+ const minLen = findLength(constraints, "minLength");
3392
+ const maxLen = findLength(constraints, "maxLength");
3393
+ if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
3394
+ addContradiction(
3395
+ ctx,
3396
+ `Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
3397
+ minLen.provenance,
3398
+ maxLen.provenance
3399
+ );
3400
+ }
3401
+ const minItems = findLength(constraints, "minItems");
3402
+ const maxItems = findLength(constraints, "maxItems");
3403
+ if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
3404
+ addContradiction(
3405
+ ctx,
3406
+ `Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
3407
+ minItems.provenance,
3408
+ maxItems.provenance
3409
+ );
3410
+ }
3411
+ }
3412
+ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
3413
+ const members = findAllowedMembers(constraints);
3414
+ if (members.length < 2) return;
3415
+ const firstSet = new Set(members[0]?.members ?? []);
3416
+ for (let i = 1; i < members.length; i++) {
3417
+ const current = members[i];
3418
+ if (current === void 0) continue;
3419
+ for (const m of firstSet) {
3420
+ if (!current.members.includes(m)) {
3421
+ firstSet.delete(m);
3422
+ }
3423
+ }
3424
+ }
3425
+ if (firstSet.size === 0) {
3426
+ const first = members[0];
3427
+ const second = members[1];
3428
+ if (first !== void 0 && second !== void 0) {
3429
+ addContradiction(
3430
+ ctx,
3431
+ `Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
3432
+ first.provenance,
3433
+ second.provenance
3434
+ );
3435
+ }
3436
+ }
3437
+ }
3438
+ function checkConstContradictions(ctx, fieldName, constraints) {
3439
+ const constConstraints = findConstConstraints(constraints);
3440
+ if (constConstraints.length < 2) return;
3441
+ const first = constConstraints[0];
3442
+ if (first === void 0) return;
3443
+ for (let i = 1; i < constConstraints.length; i++) {
3444
+ const current = constConstraints[i];
3445
+ if (current === void 0) continue;
3446
+ if (jsonValueEquals(first.value, current.value)) {
3447
+ continue;
3448
+ }
3449
+ addContradiction(
3450
+ ctx,
3451
+ `Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
3452
+ first.provenance,
3453
+ current.provenance
3454
+ );
3455
+ }
3456
+ }
3457
+ function typeLabel(type) {
3458
+ switch (type.kind) {
3459
+ case "primitive":
3460
+ return type.primitiveKind;
3461
+ case "enum":
3462
+ return "enum";
3463
+ case "array":
3464
+ return "array";
3465
+ case "object":
3466
+ return "object";
3467
+ case "record":
3468
+ return "record";
3469
+ case "union":
3470
+ return "union";
3471
+ case "reference":
3472
+ return `reference(${type.name})`;
3473
+ case "dynamic":
3474
+ return `dynamic(${type.dynamicKind})`;
3475
+ case "custom":
3476
+ return `custom(${type.typeId})`;
3477
+ default: {
3478
+ const _exhaustive = type;
3479
+ return String(_exhaustive);
3480
+ }
3481
+ }
3482
+ }
3483
+ function dereferenceType(ctx, type) {
3484
+ let current = type;
3485
+ const seen = /* @__PURE__ */ new Set();
3486
+ while (current.kind === "reference") {
3487
+ if (seen.has(current.name)) {
3488
+ return current;
3489
+ }
3490
+ seen.add(current.name);
3491
+ const definition = ctx.typeRegistry[current.name];
3492
+ if (definition === void 0) {
3493
+ return current;
3494
+ }
3495
+ current = definition.type;
3496
+ }
3497
+ return current;
3498
+ }
3499
+ function collectReferencedTypeConstraints(ctx, type) {
3500
+ const collected = [];
3501
+ let current = type;
3502
+ const seen = /* @__PURE__ */ new Set();
3503
+ while (current.kind === "reference") {
3504
+ if (seen.has(current.name)) {
3505
+ break;
3506
+ }
3507
+ seen.add(current.name);
3508
+ const definition = ctx.typeRegistry[current.name];
3509
+ if (definition === void 0) {
3510
+ break;
3511
+ }
3512
+ if (definition.constraints !== void 0) {
3513
+ collected.push(...definition.constraints);
3514
+ }
3515
+ current = definition.type;
3516
+ }
3517
+ return collected;
3518
+ }
3519
+ function resolvePathTargetType(ctx, type, segments) {
3520
+ const effectiveType = dereferenceType(ctx, type);
3521
+ if (segments.length === 0) {
3522
+ return { kind: "resolved", type: effectiveType };
3523
+ }
3524
+ if (effectiveType.kind === "array") {
3525
+ return resolvePathTargetType(ctx, effectiveType.items, segments);
3526
+ }
3527
+ if (effectiveType.kind === "object") {
3528
+ const [segment, ...rest] = segments;
3529
+ if (segment === void 0) {
3530
+ throw new Error("Invariant violation: object path traversal requires a segment");
3531
+ }
3532
+ const property = effectiveType.properties.find((prop) => prop.name === segment);
3533
+ if (property === void 0) {
3534
+ return { kind: "missing-property", segment };
3535
+ }
3536
+ return resolvePathTargetType(ctx, property.type, rest);
3537
+ }
3538
+ return { kind: "unresolvable", type: effectiveType };
3539
+ }
3540
+ function isNullType(type) {
3541
+ return type.kind === "primitive" && type.primitiveKind === "null";
3542
+ }
3543
+ function collectCustomConstraintCandidateTypes(ctx, type) {
3544
+ const effectiveType = dereferenceType(ctx, type);
3545
+ const candidates = [effectiveType];
3546
+ if (effectiveType.kind === "array") {
3547
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
3548
+ }
3549
+ if (effectiveType.kind === "union") {
3550
+ const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
3551
+ const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
3552
+ if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
3553
+ const [nullableMember] = nonNullMembers;
3554
+ if (nullableMember !== void 0) {
3555
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
3556
+ }
3557
+ }
3558
+ }
3559
+ return candidates;
3560
+ }
3561
+ function formatPathTargetFieldName(fieldName, path4) {
3562
+ return path4.length === 0 ? fieldName : `${fieldName}.${path4.join(".")}`;
3563
+ }
3564
+ function checkConstraintOnType(ctx, fieldName, type, constraint) {
3565
+ const effectiveType = dereferenceType(ctx, type);
3566
+ const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
3567
+ const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
3568
+ const isArray = effectiveType.kind === "array";
3569
+ const isEnum = effectiveType.kind === "enum";
3570
+ const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
3571
+ const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
3572
+ const label = typeLabel(effectiveType);
3573
+ const ck = constraint.constraintKind;
3574
+ switch (ck) {
3575
+ case "minimum":
3576
+ case "maximum":
3577
+ case "exclusiveMinimum":
3578
+ case "exclusiveMaximum":
3579
+ case "multipleOf": {
3580
+ if (!isNumber) {
3581
+ addTypeMismatch(
3582
+ ctx,
3583
+ `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
3584
+ constraint.provenance
3585
+ );
3586
+ }
3587
+ break;
3588
+ }
3589
+ case "minLength":
3590
+ case "maxLength":
3591
+ case "pattern": {
3592
+ if (!isString && !isStringArray) {
3593
+ addTypeMismatch(
3594
+ ctx,
3595
+ `Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
3596
+ constraint.provenance
3597
+ );
3598
+ }
3599
+ break;
3600
+ }
3601
+ case "minItems":
3602
+ case "maxItems":
3603
+ case "uniqueItems": {
3604
+ if (!isArray) {
3605
+ addTypeMismatch(
3606
+ ctx,
3607
+ `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
3608
+ constraint.provenance
3609
+ );
3610
+ }
3611
+ break;
3612
+ }
3613
+ case "allowedMembers": {
3614
+ if (!isEnum) {
3615
+ addTypeMismatch(
3616
+ ctx,
3617
+ `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
3618
+ constraint.provenance
3619
+ );
3620
+ }
3621
+ break;
3622
+ }
3623
+ case "const": {
3624
+ const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
3625
+ effectiveType.primitiveKind
3626
+ ) || effectiveType.kind === "enum";
3627
+ if (!isPrimitiveConstType) {
3628
+ addTypeMismatch(
3629
+ ctx,
3630
+ `Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
3631
+ constraint.provenance
3632
+ );
3633
+ break;
3634
+ }
3635
+ if (effectiveType.kind === "primitive") {
3636
+ const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
3637
+ const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
3638
+ if (valueType !== expectedValueType) {
3639
+ addTypeMismatch(
3640
+ ctx,
3641
+ `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
3642
+ constraint.provenance
3643
+ );
3644
+ }
3645
+ break;
3646
+ }
3647
+ const memberValues = effectiveType.members.map((member) => member.value);
3648
+ if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
3649
+ addTypeMismatch(
3650
+ ctx,
3651
+ `Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
3652
+ constraint.provenance
3653
+ );
3654
+ }
3655
+ break;
3656
+ }
3657
+ case "custom": {
3658
+ checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
3659
+ break;
3660
+ }
3661
+ default: {
3662
+ const _exhaustive = constraint;
3663
+ throw new Error(
3664
+ `Unhandled constraint kind: ${_exhaustive.constraintKind}`
3665
+ );
3666
+ }
3667
+ }
3668
+ }
3669
+ function checkTypeApplicability(ctx, fieldName, type, constraints) {
3670
+ for (const constraint of constraints) {
3671
+ if (constraint.path) {
3672
+ const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
3673
+ const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
3674
+ if (resolution.kind === "missing-property") {
3675
+ addUnknownPathTarget(
3676
+ ctx,
3677
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
3678
+ constraint.provenance
3679
+ );
3680
+ continue;
3681
+ }
3682
+ if (resolution.kind === "unresolvable") {
3683
+ addTypeMismatch(
3684
+ ctx,
3685
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
3686
+ constraint.provenance
3687
+ );
3688
+ continue;
3689
+ }
3690
+ checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
3691
+ continue;
3692
+ }
3693
+ checkConstraintOnType(ctx, fieldName, type, constraint);
3694
+ }
3695
+ }
3696
+ function checkCustomConstraint(ctx, fieldName, type, constraint) {
3697
+ if (ctx.extensionRegistry === void 0) return;
3698
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3699
+ if (registration === void 0) {
3700
+ addUnknownExtension(
3701
+ ctx,
3702
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
3703
+ constraint.provenance
3704
+ );
3705
+ return;
3706
+ }
3707
+ const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
3708
+ const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core4.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
3709
+ if (normalizedTagName !== void 0) {
3710
+ const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
3711
+ const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
3712
+ if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
3713
+ (candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
3714
+ )) {
3715
+ addTypeMismatch(
3716
+ ctx,
3717
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3718
+ constraint.provenance
3719
+ );
3720
+ return;
3721
+ }
3722
+ }
3723
+ if (registration.applicableTypes === null) {
3724
+ if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
3725
+ addTypeMismatch(
3726
+ ctx,
3727
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3728
+ constraint.provenance
3729
+ );
3730
+ }
3731
+ return;
3732
+ }
3733
+ const applicableTypes = registration.applicableTypes;
3734
+ const matchesApplicableType = candidateTypes.some(
3735
+ (candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
3736
+ );
3737
+ if (!matchesApplicableType) {
3738
+ addTypeMismatch(
3739
+ ctx,
3740
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3741
+ constraint.provenance
3742
+ );
3743
+ }
3744
+ }
3745
+ function validateFieldNode(ctx, field) {
3746
+ validateConstraints(ctx, field.name, field.type, [
3747
+ ...collectReferencedTypeConstraints(ctx, field.type),
3748
+ ...field.constraints
3749
+ ]);
3750
+ if (field.type.kind === "object") {
3751
+ for (const prop of field.type.properties) {
3752
+ validateObjectProperty(ctx, field.name, prop);
3753
+ }
3754
+ }
3755
+ }
3756
+ function validateObjectProperty(ctx, parentName, prop) {
3757
+ const qualifiedName = `${parentName}.${prop.name}`;
3758
+ validateConstraints(ctx, qualifiedName, prop.type, [
3759
+ ...collectReferencedTypeConstraints(ctx, prop.type),
3760
+ ...prop.constraints
3761
+ ]);
3762
+ if (prop.type.kind === "object") {
3763
+ for (const nestedProp of prop.type.properties) {
3764
+ validateObjectProperty(ctx, qualifiedName, nestedProp);
3765
+ }
3766
+ }
3767
+ }
3768
+ function validateConstraints(ctx, name, type, constraints) {
3769
+ checkNumericContradictions(ctx, name, constraints);
3770
+ checkLengthContradictions(ctx, name, constraints);
3771
+ checkAllowedMembersContradiction(ctx, name, constraints);
3772
+ checkConstContradictions(ctx, name, constraints);
3773
+ checkConstraintBroadening(ctx, name, constraints);
3774
+ checkCustomConstraintSemantics(ctx, name, constraints);
3775
+ checkTypeApplicability(ctx, name, type, constraints);
3776
+ }
3777
+ function validateElement(ctx, element) {
3778
+ switch (element.kind) {
3779
+ case "field":
3780
+ validateFieldNode(ctx, element);
3781
+ break;
3782
+ case "group":
3783
+ for (const child of element.elements) {
3784
+ validateElement(ctx, child);
3785
+ }
3786
+ break;
3787
+ case "conditional":
3788
+ for (const child of element.elements) {
3789
+ validateElement(ctx, child);
3790
+ }
3791
+ break;
3792
+ default: {
3793
+ const _exhaustive = element;
3794
+ throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
3795
+ }
3796
+ }
3797
+ }
3798
+ function validateIR(ir, options) {
3799
+ const ctx = {
3800
+ diagnostics: [],
3801
+ extensionRegistry: options?.extensionRegistry,
3802
+ typeRegistry: ir.typeRegistry
3803
+ };
3804
+ for (const element of ir.elements) {
3805
+ validateElement(ctx, element);
3806
+ }
3807
+ return {
3808
+ diagnostics: ctx.diagnostics,
3809
+ valid: ctx.diagnostics.every((d) => d.severity !== "error")
3810
+ };
3811
+ }
3812
+ var import_core4;
3813
+ var init_constraint_validator = __esm({
3814
+ "src/validate/constraint-validator.ts"() {
3815
+ "use strict";
3816
+ import_core4 = require("@formspec/core");
3817
+ }
3818
+ });
3819
+
3820
+ // src/validate/index.ts
3821
+ var init_validate = __esm({
3822
+ "src/validate/index.ts"() {
3823
+ "use strict";
3824
+ init_constraint_validator();
3825
+ }
3826
+ });
3827
+
2463
3828
  // src/generators/class-schema.ts
2464
- function generateClassSchemas(analysis, source) {
3829
+ function generateClassSchemas(analysis, source, options) {
2465
3830
  const ir = canonicalizeTSDoc(analysis, source);
3831
+ const validationResult = validateIR(ir, {
3832
+ ...options?.extensionRegistry !== void 0 && {
3833
+ extensionRegistry: options.extensionRegistry
3834
+ },
3835
+ ...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
3836
+ });
3837
+ if (!validationResult.valid) {
3838
+ throw new Error(formatValidationError(validationResult.diagnostics));
3839
+ }
2466
3840
  return {
2467
- jsonSchema: generateJsonSchemaFromIR(ir),
3841
+ jsonSchema: generateJsonSchemaFromIR(ir, options),
2468
3842
  uiSchema: generateUiSchemaFromIR(ir)
2469
3843
  };
2470
3844
  }
3845
+ function formatValidationError(diagnostics) {
3846
+ const lines = diagnostics.map((diagnostic) => {
3847
+ const primary = formatLocation(diagnostic.primaryLocation);
3848
+ const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
3849
+ return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
3850
+ });
3851
+ return `FormSpec validation failed:
3852
+ ${lines.map((line) => `- ${line}`).join("\n")}`;
3853
+ }
3854
+ function formatLocation(location) {
3855
+ return `${location.file}:${String(location.line)}:${String(location.column)}`;
3856
+ }
2471
3857
  function generateSchemasFromClass(options) {
2472
3858
  const ctx = createProgramContext(options.filePath);
2473
3859
  const classDecl = findClassByName(ctx.sourceFile, options.className);
2474
3860
  if (!classDecl) {
2475
3861
  throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
2476
3862
  }
2477
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2478
- return generateClassSchemas(analysis, { file: options.filePath });
3863
+ const analysis = analyzeClassToIR(
3864
+ classDecl,
3865
+ ctx.checker,
3866
+ options.filePath,
3867
+ options.extensionRegistry
3868
+ );
3869
+ return generateClassSchemas(
3870
+ analysis,
3871
+ { file: options.filePath },
3872
+ {
3873
+ extensionRegistry: options.extensionRegistry,
3874
+ vendorPrefix: options.vendorPrefix
3875
+ }
3876
+ );
2479
3877
  }
2480
3878
  function generateSchemas(options) {
2481
- const ctx = createProgramContext(options.filePath);
2482
- const source = { file: options.filePath };
2483
- const classDecl = findClassByName(ctx.sourceFile, options.typeName);
2484
- if (classDecl) {
2485
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2486
- return generateClassSchemas(analysis, source);
2487
- }
2488
- const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
2489
- if (interfaceDecl) {
2490
- const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
2491
- return generateClassSchemas(analysis, source);
2492
- }
2493
- const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
2494
- if (typeAlias) {
2495
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
2496
- if (result.ok) {
2497
- return generateClassSchemas(result.analysis, source);
2498
- }
2499
- throw new Error(result.error);
2500
- }
2501
- throw new Error(
2502
- `Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
3879
+ const analysis = analyzeNamedTypeToIR(
3880
+ options.filePath,
3881
+ options.typeName,
3882
+ options.extensionRegistry
2503
3883
  );
3884
+ return generateClassSchemas(analysis, { file: options.filePath }, options);
2504
3885
  }
2505
3886
  var init_class_schema = __esm({
2506
3887
  "src/generators/class-schema.ts"() {
@@ -2510,13 +3891,14 @@ var init_class_schema = __esm({
2510
3891
  init_canonicalize();
2511
3892
  init_ir_generator();
2512
3893
  init_ir_generator2();
3894
+ init_validate();
2513
3895
  }
2514
3896
  });
2515
3897
 
2516
3898
  // src/generators/mixed-authoring.ts
2517
3899
  function buildMixedAuthoringSchemas(options) {
2518
3900
  const { filePath, typeName, overlays, ...schemaOptions } = options;
2519
- const analysis = analyzeNamedType(filePath, typeName);
3901
+ const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
2520
3902
  const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2521
3903
  const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2522
3904
  return {
@@ -2524,29 +3906,6 @@ function buildMixedAuthoringSchemas(options) {
2524
3906
  uiSchema: generateUiSchemaFromIR(ir)
2525
3907
  };
2526
3908
  }
2527
- function analyzeNamedType(filePath, typeName) {
2528
- const ctx = createProgramContext(filePath);
2529
- const source = { file: filePath };
2530
- const classDecl = findClassByName(ctx.sourceFile, typeName);
2531
- if (classDecl !== null) {
2532
- return analyzeClassToIR(classDecl, ctx.checker, source.file);
2533
- }
2534
- const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2535
- if (interfaceDecl !== null) {
2536
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2537
- }
2538
- const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2539
- if (typeAlias !== null) {
2540
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2541
- if (result.ok) {
2542
- return result.analysis;
2543
- }
2544
- throw new Error(result.error);
2545
- }
2546
- throw new Error(
2547
- `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2548
- );
2549
- }
2550
3909
  function composeAnalysisWithOverlays(analysis, overlays) {
2551
3910
  const overlayIR = canonicalizeChainDSL(overlays);
2552
3911
  const overlayFields = collectOverlayFields(overlayIR.elements);
@@ -2616,7 +3975,7 @@ function assertSupportedOverlayField(baseField, overlayField) {
2616
3975
  `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2617
3976
  );
2618
3977
  }
2619
- if (overlayField.required) {
3978
+ if (overlayField.required && !baseField.required) {
2620
3979
  throw new Error(
2621
3980
  `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2622
3981
  );
@@ -2706,7 +4065,7 @@ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2706
4065
  const overlayOnly = overlayAnnotations.filter(
2707
4066
  (annotation) => !baseKeys.has(annotationKey(annotation))
2708
4067
  );
2709
- return [...overlayOnly, ...baseAnnotations];
4068
+ return [...baseAnnotations, ...overlayOnly];
2710
4069
  }
2711
4070
  function annotationKey(annotation) {
2712
4071
  return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
@@ -2718,7 +4077,6 @@ var init_mixed_authoring = __esm({
2718
4077
  init_ir_generator2();
2719
4078
  init_canonicalize();
2720
4079
  init_program();
2721
- init_class_analyzer();
2722
4080
  }
2723
4081
  });
2724
4082