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