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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +74 -128
  2. package/dist/__tests__/class-schema.test.d.ts +2 -0
  3. package/dist/__tests__/class-schema.test.d.ts.map +1 -0
  4. package/dist/__tests__/date-extension.integration.test.d.ts +2 -0
  5. package/dist/__tests__/date-extension.integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures/class-schema-regressions.d.ts +83 -0
  7. package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -0
  8. package/dist/__tests__/fixtures/example-date-extension.d.ts +12 -0
  9. package/dist/__tests__/fixtures/example-date-extension.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
  11. package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
  12. package/dist/__tests__/fixtures/extension-forms.d.ts +7 -0
  13. package/dist/__tests__/fixtures/extension-forms.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +1 -0
  15. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -1
  16. package/dist/__tests__/fixtures/named-primitive-aliases.d.ts +15 -0
  17. package/dist/__tests__/fixtures/named-primitive-aliases.d.ts.map +1 -0
  18. package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts +14 -0
  19. package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts.map +1 -0
  20. package/dist/__tests__/fixtures/sample-forms.d.ts +10 -0
  21. package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -1
  22. package/dist/__tests__/generate-schemas.test.d.ts +2 -0
  23. package/dist/__tests__/generate-schemas.test.d.ts.map +1 -0
  24. package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
  25. package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
  26. package/dist/__tests__/parity/parity.test.d.ts +6 -2
  27. package/dist/__tests__/parity/parity.test.d.ts.map +1 -1
  28. package/dist/__tests__/parity/utils.d.ts +9 -4
  29. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  30. package/dist/analyzer/class-analyzer.d.ts +5 -4
  31. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  32. package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
  33. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  34. package/dist/analyzer/program.d.ts +15 -0
  35. package/dist/analyzer/program.d.ts.map +1 -1
  36. package/dist/analyzer/tsdoc-parser.d.ts +23 -2
  37. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  38. package/dist/browser.cjs +269 -11
  39. package/dist/browser.cjs.map +1 -1
  40. package/dist/browser.js +269 -11
  41. package/dist/browser.js.map +1 -1
  42. package/dist/build.d.ts +28 -2
  43. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  44. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  45. package/dist/cli.cjs +1640 -282
  46. package/dist/cli.cjs.map +1 -1
  47. package/dist/cli.js +1638 -281
  48. package/dist/cli.js.map +1 -1
  49. package/dist/extensions/registry.d.ts +25 -1
  50. package/dist/extensions/registry.d.ts.map +1 -1
  51. package/dist/generators/class-schema.d.ts +4 -4
  52. package/dist/generators/class-schema.d.ts.map +1 -1
  53. package/dist/generators/method-schema.d.ts.map +1 -1
  54. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  55. package/dist/index.cjs +1615 -271
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.js +1615 -271
  58. package/dist/index.js.map +1 -1
  59. package/dist/internals.cjs +990 -236
  60. package/dist/internals.cjs.map +1 -1
  61. package/dist/internals.js +988 -234
  62. package/dist/internals.js.map +1 -1
  63. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  64. package/dist/validate/constraint-validator.d.ts.map +1 -1
  65. package/package.json +3 -3
package/dist/index.cjs CHANGED
@@ -82,6 +82,7 @@ function canonicalizeChainDSL(form) {
82
82
  kind: "form-ir",
83
83
  irVersion: import_core.IR_VERSION,
84
84
  elements: canonicalizeElements(form.elements),
85
+ rootAnnotations: [],
85
86
  typeRegistry: {},
86
87
  provenance: CHAIN_DSL_PROVENANCE
87
88
  };
@@ -387,6 +388,7 @@ function canonicalizeTSDoc(analysis, source) {
387
388
  irVersion: import_core2.IR_VERSION,
388
389
  elements,
389
390
  typeRegistry: analysis.typeRegistry,
391
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
390
392
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
391
393
  provenance
392
394
  };
@@ -464,6 +466,9 @@ function generateJsonSchemaFromIR(ir, options) {
464
466
  const ctx = makeContext(options);
465
467
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
466
468
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
469
+ if (typeDef.constraints && typeDef.constraints.length > 0) {
470
+ applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
471
+ }
467
472
  if (typeDef.annotations && typeDef.annotations.length > 0) {
468
473
  applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
469
474
  }
@@ -632,7 +637,9 @@ function generateTypeNode(type, ctx) {
632
637
  }
633
638
  }
634
639
  function generatePrimitiveType(type) {
635
- return { type: type.primitiveKind };
640
+ return {
641
+ type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
642
+ };
636
643
  }
637
644
  function generateEnumType(type) {
638
645
  const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
@@ -805,7 +812,7 @@ function applyAnnotations(schema, annotations, ctx) {
805
812
  case "deprecated":
806
813
  schema.deprecated = true;
807
814
  if (annotation.message !== void 0 && annotation.message !== "") {
808
- schema["x-formspec-deprecation-description"] = annotation.message;
815
+ schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
809
816
  }
810
817
  break;
811
818
  case "placeholder":
@@ -838,7 +845,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
838
845
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
839
846
  );
840
847
  }
841
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
848
+ assignVendorPrefixedExtensionKeywords(
849
+ schema,
850
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
851
+ ctx.vendorPrefix,
852
+ `custom constraint "${constraint.constraintId}"`
853
+ );
842
854
  }
843
855
  function applyCustomAnnotation(schema, annotation, ctx) {
844
856
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -850,7 +862,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
850
862
  if (registration.toJsonSchema === void 0) {
851
863
  return;
852
864
  }
853
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
865
+ assignVendorPrefixedExtensionKeywords(
866
+ schema,
867
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
868
+ ctx.vendorPrefix,
869
+ `custom annotation "${annotation.annotationId}"`
870
+ );
871
+ }
872
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
873
+ for (const [key, value] of Object.entries(extensionSchema)) {
874
+ if (!key.startsWith(`${vendorPrefix}-`)) {
875
+ throw new Error(
876
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
877
+ );
878
+ }
879
+ schema[key] = value;
880
+ }
854
881
  }
855
882
 
856
883
  // src/json-schema/generator.ts
@@ -1103,7 +1130,10 @@ function getSchemaExtension(schema, key) {
1103
1130
  // src/extensions/registry.ts
1104
1131
  function createExtensionRegistry(extensions) {
1105
1132
  const typeMap = /* @__PURE__ */ new Map();
1133
+ const typeNameMap = /* @__PURE__ */ new Map();
1106
1134
  const constraintMap = /* @__PURE__ */ new Map();
1135
+ const constraintTagMap = /* @__PURE__ */ new Map();
1136
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
1107
1137
  const annotationMap = /* @__PURE__ */ new Map();
1108
1138
  for (const ext of extensions) {
1109
1139
  if (ext.types !== void 0) {
@@ -1113,6 +1143,27 @@ function createExtensionRegistry(extensions) {
1113
1143
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1114
1144
  }
1115
1145
  typeMap.set(qualifiedId, type);
1146
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
1147
+ if (typeNameMap.has(sourceTypeName)) {
1148
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
1149
+ }
1150
+ typeNameMap.set(sourceTypeName, {
1151
+ extensionId: ext.extensionId,
1152
+ registration: type
1153
+ });
1154
+ }
1155
+ if (type.builtinConstraintBroadenings !== void 0) {
1156
+ for (const broadening of type.builtinConstraintBroadenings) {
1157
+ const key = `${qualifiedId}:${broadening.tagName}`;
1158
+ if (builtinBroadeningMap.has(key)) {
1159
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
1160
+ }
1161
+ builtinBroadeningMap.set(key, {
1162
+ extensionId: ext.extensionId,
1163
+ registration: broadening
1164
+ });
1165
+ }
1166
+ }
1116
1167
  }
1117
1168
  }
1118
1169
  if (ext.constraints !== void 0) {
@@ -1124,6 +1175,17 @@ function createExtensionRegistry(extensions) {
1124
1175
  constraintMap.set(qualifiedId, constraint);
1125
1176
  }
1126
1177
  }
1178
+ if (ext.constraintTags !== void 0) {
1179
+ for (const tag of ext.constraintTags) {
1180
+ if (constraintTagMap.has(tag.tagName)) {
1181
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
1182
+ }
1183
+ constraintTagMap.set(tag.tagName, {
1184
+ extensionId: ext.extensionId,
1185
+ registration: tag
1186
+ });
1187
+ }
1188
+ }
1127
1189
  if (ext.annotations !== void 0) {
1128
1190
  for (const annotation of ext.annotations) {
1129
1191
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -1137,7 +1199,10 @@ function createExtensionRegistry(extensions) {
1137
1199
  return {
1138
1200
  extensions,
1139
1201
  findType: (typeId) => typeMap.get(typeId),
1202
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
1140
1203
  findConstraint: (constraintId) => constraintMap.get(constraintId),
1204
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
1205
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
1141
1206
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
1142
1207
  };
1143
1208
  }
@@ -1205,85 +1270,17 @@ var jsonSchema7Schema = import_zod3.z.lazy(
1205
1270
  );
1206
1271
 
1207
1272
  // src/analyzer/program.ts
1208
- var ts = __toESM(require("typescript"), 1);
1273
+ var ts4 = __toESM(require("typescript"), 1);
1209
1274
  var path = __toESM(require("path"), 1);
1210
- function createProgramContext(filePath) {
1211
- const absolutePath = path.resolve(filePath);
1212
- const fileDir = path.dirname(absolutePath);
1213
- const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
1214
- let compilerOptions;
1215
- let fileNames;
1216
- if (configPath) {
1217
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
1218
- if (configFile.error) {
1219
- throw new Error(
1220
- `Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
1221
- );
1222
- }
1223
- const parsed = ts.parseJsonConfigFileContent(
1224
- configFile.config,
1225
- ts.sys,
1226
- path.dirname(configPath)
1227
- );
1228
- if (parsed.errors.length > 0) {
1229
- const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
1230
- throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
1231
- }
1232
- compilerOptions = parsed.options;
1233
- fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
1234
- } else {
1235
- compilerOptions = {
1236
- target: ts.ScriptTarget.ES2022,
1237
- module: ts.ModuleKind.NodeNext,
1238
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
1239
- strict: true,
1240
- skipLibCheck: true,
1241
- declaration: true
1242
- };
1243
- fileNames = [absolutePath];
1244
- }
1245
- const program = ts.createProgram(fileNames, compilerOptions);
1246
- const sourceFile = program.getSourceFile(absolutePath);
1247
- if (!sourceFile) {
1248
- throw new Error(`Could not find source file: ${absolutePath}`);
1249
- }
1250
- return {
1251
- program,
1252
- checker: program.getTypeChecker(),
1253
- sourceFile
1254
- };
1255
- }
1256
- function findNodeByName(sourceFile, name, predicate, getName) {
1257
- let result = null;
1258
- function visit(node) {
1259
- if (result) return;
1260
- if (predicate(node) && getName(node) === name) {
1261
- result = node;
1262
- return;
1263
- }
1264
- ts.forEachChild(node, visit);
1265
- }
1266
- visit(sourceFile);
1267
- return result;
1268
- }
1269
- function findClassByName(sourceFile, className) {
1270
- return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
1271
- }
1272
- function findInterfaceByName(sourceFile, interfaceName) {
1273
- return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
1274
- }
1275
- function findTypeAliasByName(sourceFile, aliasName) {
1276
- return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
1277
- }
1278
1275
 
1279
1276
  // src/analyzer/class-analyzer.ts
1280
- var ts4 = __toESM(require("typescript"), 1);
1277
+ var ts3 = __toESM(require("typescript"), 1);
1281
1278
 
1282
1279
  // src/analyzer/jsdoc-constraints.ts
1283
- var ts3 = __toESM(require("typescript"), 1);
1280
+ var ts2 = __toESM(require("typescript"), 1);
1284
1281
 
1285
1282
  // src/analyzer/tsdoc-parser.ts
1286
- var ts2 = __toESM(require("typescript"), 1);
1283
+ var ts = __toESM(require("typescript"), 1);
1287
1284
  var import_tsdoc = require("@microsoft/tsdoc");
1288
1285
  var import_core3 = require("@formspec/core");
1289
1286
 
@@ -1311,7 +1308,7 @@ var LENGTH_CONSTRAINT_MAP = {
1311
1308
  maxItems: "maxItems"
1312
1309
  };
1313
1310
  var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1314
- function createFormSpecTSDocConfig() {
1311
+ function createFormSpecTSDocConfig(extensionTagNames = []) {
1315
1312
  const config = new import_tsdoc.TSDocConfiguration();
1316
1313
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
1317
1314
  config.addTagDefinition(
@@ -1331,14 +1328,34 @@ function createFormSpecTSDocConfig() {
1331
1328
  })
1332
1329
  );
1333
1330
  }
1331
+ for (const tagName of extensionTagNames) {
1332
+ config.addTagDefinition(
1333
+ new import_tsdoc.TSDocTagDefinition({
1334
+ tagName: "@" + tagName,
1335
+ syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
1336
+ allowMultiple: true
1337
+ })
1338
+ );
1339
+ }
1334
1340
  return config;
1335
1341
  }
1336
- var sharedParser;
1337
- function getParser() {
1338
- sharedParser ??= new import_tsdoc.TSDocParser(createFormSpecTSDocConfig());
1339
- return sharedParser;
1340
- }
1341
- function parseTSDocTags(node, file = "") {
1342
+ var parserCache = /* @__PURE__ */ new Map();
1343
+ function getParser(options) {
1344
+ const extensionTagNames = [
1345
+ ...options?.extensionRegistry?.extensions.flatMap(
1346
+ (extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
1347
+ ) ?? []
1348
+ ].sort();
1349
+ const cacheKey = extensionTagNames.join("|");
1350
+ const existing = parserCache.get(cacheKey);
1351
+ if (existing) {
1352
+ return existing;
1353
+ }
1354
+ const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
1355
+ parserCache.set(cacheKey, parser);
1356
+ return parser;
1357
+ }
1358
+ function parseTSDocTags(node, file = "", options) {
1342
1359
  const constraints = [];
1343
1360
  const annotations = [];
1344
1361
  let displayName;
@@ -1349,17 +1366,17 @@ function parseTSDocTags(node, file = "") {
1349
1366
  let placeholderProvenance;
1350
1367
  const sourceFile = node.getSourceFile();
1351
1368
  const sourceText = sourceFile.getFullText();
1352
- const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
1369
+ const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
1353
1370
  if (commentRanges) {
1354
1371
  for (const range of commentRanges) {
1355
- if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
1372
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
1356
1373
  continue;
1357
1374
  }
1358
1375
  const commentText = sourceText.substring(range.pos, range.end);
1359
1376
  if (!commentText.startsWith("/**")) {
1360
1377
  continue;
1361
1378
  }
1362
- const parser = getParser();
1379
+ const parser = getParser(options);
1363
1380
  const parserContext = parser.parseRange(
1364
1381
  import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
1365
1382
  );
@@ -1370,26 +1387,31 @@ function parseTSDocTags(node, file = "") {
1370
1387
  const text2 = extractBlockText(block).trim();
1371
1388
  if (text2 === "") continue;
1372
1389
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1373
- if (tagName === "displayName") {
1374
- if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1375
- displayName = text2;
1376
- displayNameProvenance = provenance2;
1377
- }
1378
- } else if (tagName === "format") {
1379
- annotations.push({
1380
- kind: "annotation",
1381
- annotationKind: "format",
1382
- value: text2,
1383
- provenance: provenance2
1384
- });
1385
- } else {
1386
- if (tagName === "description" && description === void 0) {
1390
+ switch (tagName) {
1391
+ case "displayName":
1392
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1393
+ displayName = text2;
1394
+ displayNameProvenance = provenance2;
1395
+ }
1396
+ break;
1397
+ case "format":
1398
+ annotations.push({
1399
+ kind: "annotation",
1400
+ annotationKind: "format",
1401
+ value: text2,
1402
+ provenance: provenance2
1403
+ });
1404
+ break;
1405
+ case "description":
1387
1406
  description = text2;
1388
1407
  descriptionProvenance = provenance2;
1389
- } else if (tagName === "placeholder" && placeholder === void 0) {
1390
- placeholder = text2;
1391
- placeholderProvenance = provenance2;
1392
- }
1408
+ break;
1409
+ case "placeholder":
1410
+ if (placeholder === void 0) {
1411
+ placeholder = text2;
1412
+ placeholderProvenance = provenance2;
1413
+ }
1414
+ break;
1393
1415
  }
1394
1416
  continue;
1395
1417
  }
@@ -1398,7 +1420,7 @@ function parseTSDocTags(node, file = "") {
1398
1420
  const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1399
1421
  if (text === "" && expectedType !== "boolean") continue;
1400
1422
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1401
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1423
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1402
1424
  if (constraintNode) {
1403
1425
  constraints.push(constraintNode);
1404
1426
  }
@@ -1419,6 +1441,13 @@ function parseTSDocTags(node, file = "") {
1419
1441
  descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1420
1442
  }
1421
1443
  }
1444
+ if (description === void 0) {
1445
+ const summary = extractPlainText(docComment.summarySection).trim();
1446
+ if (summary !== "") {
1447
+ description = summary;
1448
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
1449
+ }
1450
+ }
1422
1451
  }
1423
1452
  }
1424
1453
  if (displayName !== void 0 && displayNameProvenance !== void 0) {
@@ -1445,7 +1474,7 @@ function parseTSDocTags(node, file = "") {
1445
1474
  provenance: placeholderProvenance
1446
1475
  });
1447
1476
  }
1448
- const jsDocTagsAll = ts2.getJSDocTags(node);
1477
+ const jsDocTagsAll = ts.getJSDocTags(node);
1449
1478
  for (const tag of jsDocTagsAll) {
1450
1479
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1451
1480
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
@@ -1458,7 +1487,7 @@ function parseTSDocTags(node, file = "") {
1458
1487
  annotations.push(defaultValueNode);
1459
1488
  continue;
1460
1489
  }
1461
- const constraintNode = parseConstraintValue(tagName, text, provenance);
1490
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
1462
1491
  if (constraintNode) {
1463
1492
  constraints.push(constraintNode);
1464
1493
  }
@@ -1468,7 +1497,7 @@ function parseTSDocTags(node, file = "") {
1468
1497
  function extractDisplayNameMetadata(node) {
1469
1498
  let displayName;
1470
1499
  const memberDisplayNames = /* @__PURE__ */ new Map();
1471
- for (const tag of ts2.getJSDocTags(node)) {
1500
+ for (const tag of ts.getJSDocTags(node)) {
1472
1501
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1473
1502
  if (tagName !== "displayName") continue;
1474
1503
  const commentText = getTagCommentText(tag);
@@ -1489,11 +1518,11 @@ function extractDisplayNameMetadata(node) {
1489
1518
  }
1490
1519
  function extractPathTarget(text) {
1491
1520
  const trimmed = text.trimStart();
1492
- const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
1493
- if (!match?.[1] || !match[2]) return null;
1521
+ const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
1522
+ if (!match?.[1]) return null;
1494
1523
  return {
1495
1524
  path: { segments: [match[1]] },
1496
- remainingText: match[2]
1525
+ remainingText: match[2] ?? ""
1497
1526
  };
1498
1527
  }
1499
1528
  function extractBlockText(block) {
@@ -1514,7 +1543,11 @@ function extractPlainText(node) {
1514
1543
  }
1515
1544
  return result;
1516
1545
  }
1517
- function parseConstraintValue(tagName, text, provenance) {
1546
+ function parseConstraintValue(tagName, text, provenance, options) {
1547
+ const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
1548
+ if (customConstraint) {
1549
+ return customConstraint;
1550
+ }
1518
1551
  if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1519
1552
  return null;
1520
1553
  }
@@ -1619,6 +1652,83 @@ function parseConstraintValue(tagName, text, provenance) {
1619
1652
  provenance
1620
1653
  };
1621
1654
  }
1655
+ function parseExtensionConstraintValue(tagName, text, provenance, options) {
1656
+ const pathResult = extractPathTarget(text);
1657
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1658
+ const path3 = pathResult?.path;
1659
+ const registry = options?.extensionRegistry;
1660
+ if (registry === void 0) {
1661
+ return null;
1662
+ }
1663
+ const directTag = registry.findConstraintTag(tagName);
1664
+ if (directTag !== void 0) {
1665
+ return makeCustomConstraintNode(
1666
+ directTag.extensionId,
1667
+ directTag.registration.constraintName,
1668
+ directTag.registration.parseValue(effectiveText),
1669
+ provenance,
1670
+ path3,
1671
+ registry
1672
+ );
1673
+ }
1674
+ if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1675
+ return null;
1676
+ }
1677
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
1678
+ if (broadenedTypeId === void 0) {
1679
+ return null;
1680
+ }
1681
+ const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
1682
+ if (broadened === void 0) {
1683
+ return null;
1684
+ }
1685
+ return makeCustomConstraintNode(
1686
+ broadened.extensionId,
1687
+ broadened.registration.constraintName,
1688
+ broadened.registration.parseValue(effectiveText),
1689
+ provenance,
1690
+ path3,
1691
+ registry
1692
+ );
1693
+ }
1694
+ function getBroadenedCustomTypeId(fieldType) {
1695
+ if (fieldType?.kind === "custom") {
1696
+ return fieldType.typeId;
1697
+ }
1698
+ if (fieldType?.kind !== "union") {
1699
+ return void 0;
1700
+ }
1701
+ const customMembers = fieldType.members.filter(
1702
+ (member) => member.kind === "custom"
1703
+ );
1704
+ if (customMembers.length !== 1) {
1705
+ return void 0;
1706
+ }
1707
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
1708
+ const allOtherMembersAreNull = nonCustomMembers.every(
1709
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
1710
+ );
1711
+ const customMember = customMembers[0];
1712
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
1713
+ }
1714
+ function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path3, registry) {
1715
+ const constraintId = `${extensionId}/${constraintName}`;
1716
+ const registration = registry.findConstraint(constraintId);
1717
+ if (registration === void 0) {
1718
+ throw new Error(
1719
+ `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
1720
+ );
1721
+ }
1722
+ return {
1723
+ kind: "constraint",
1724
+ constraintKind: "custom",
1725
+ constraintId,
1726
+ payload,
1727
+ compositionRule: registration.compositionRule,
1728
+ ...path3 && { path: path3 },
1729
+ provenance
1730
+ };
1731
+ }
1622
1732
  function parseDefaultValueValue(text, provenance) {
1623
1733
  const trimmed = text.trim();
1624
1734
  let value;
@@ -1675,33 +1785,33 @@ function getTagCommentText(tag) {
1675
1785
  if (typeof tag.comment === "string") {
1676
1786
  return tag.comment;
1677
1787
  }
1678
- return ts2.getTextOfJSDocComment(tag.comment);
1788
+ return ts.getTextOfJSDocComment(tag.comment);
1679
1789
  }
1680
1790
 
1681
1791
  // src/analyzer/jsdoc-constraints.ts
1682
- function extractJSDocConstraintNodes(node, file = "") {
1683
- const result = parseTSDocTags(node, file);
1792
+ function extractJSDocConstraintNodes(node, file = "", options) {
1793
+ const result = parseTSDocTags(node, file, options);
1684
1794
  return [...result.constraints];
1685
1795
  }
1686
- function extractJSDocAnnotationNodes(node, file = "") {
1687
- const result = parseTSDocTags(node, file);
1796
+ function extractJSDocAnnotationNodes(node, file = "", options) {
1797
+ const result = parseTSDocTags(node, file, options);
1688
1798
  return [...result.annotations];
1689
1799
  }
1690
1800
  function extractDefaultValueAnnotation(initializer, file = "") {
1691
1801
  if (!initializer) return null;
1692
1802
  let value;
1693
- if (ts3.isStringLiteral(initializer)) {
1803
+ if (ts2.isStringLiteral(initializer)) {
1694
1804
  value = initializer.text;
1695
- } else if (ts3.isNumericLiteral(initializer)) {
1805
+ } else if (ts2.isNumericLiteral(initializer)) {
1696
1806
  value = Number(initializer.text);
1697
- } else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
1807
+ } else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
1698
1808
  value = true;
1699
- } else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
1809
+ } else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
1700
1810
  value = false;
1701
- } else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
1811
+ } else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
1702
1812
  value = null;
1703
- } else if (ts3.isPrefixUnaryExpression(initializer)) {
1704
- if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
1813
+ } else if (ts2.isPrefixUnaryExpression(initializer)) {
1814
+ if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
1705
1815
  value = -Number(initializer.operand.text);
1706
1816
  }
1707
1817
  }
@@ -1723,36 +1833,56 @@ function extractDefaultValueAnnotation(initializer, file = "") {
1723
1833
 
1724
1834
  // src/analyzer/class-analyzer.ts
1725
1835
  function isObjectType(type) {
1726
- return !!(type.flags & ts4.TypeFlags.Object);
1836
+ return !!(type.flags & ts3.TypeFlags.Object);
1727
1837
  }
1728
1838
  function isTypeReference(type) {
1729
- return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1839
+ return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
1730
1840
  }
1731
1841
  var RESOLVING_TYPE_PLACEHOLDER = {
1732
1842
  kind: "object",
1733
1843
  properties: [],
1734
1844
  additionalProperties: true
1735
1845
  };
1736
- function analyzeClassToIR(classDecl, checker, file = "") {
1846
+ function makeParseOptions(extensionRegistry, fieldType) {
1847
+ if (extensionRegistry === void 0 && fieldType === void 0) {
1848
+ return void 0;
1849
+ }
1850
+ return {
1851
+ ...extensionRegistry !== void 0 && { extensionRegistry },
1852
+ ...fieldType !== void 0 && { fieldType }
1853
+ };
1854
+ }
1855
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1737
1856
  const name = classDecl.name?.text ?? "AnonymousClass";
1738
1857
  const fields = [];
1739
1858
  const fieldLayouts = [];
1740
1859
  const typeRegistry = {};
1741
- const annotations = extractJSDocAnnotationNodes(classDecl, file);
1860
+ const annotations = extractJSDocAnnotationNodes(
1861
+ classDecl,
1862
+ file,
1863
+ makeParseOptions(extensionRegistry)
1864
+ );
1742
1865
  const visiting = /* @__PURE__ */ new Set();
1743
1866
  const instanceMethods = [];
1744
1867
  const staticMethods = [];
1745
1868
  for (const member of classDecl.members) {
1746
- if (ts4.isPropertyDeclaration(member)) {
1747
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1869
+ if (ts3.isPropertyDeclaration(member)) {
1870
+ const fieldNode = analyzeFieldToIR(
1871
+ member,
1872
+ checker,
1873
+ file,
1874
+ typeRegistry,
1875
+ visiting,
1876
+ extensionRegistry
1877
+ );
1748
1878
  if (fieldNode) {
1749
1879
  fields.push(fieldNode);
1750
1880
  fieldLayouts.push({});
1751
1881
  }
1752
- } else if (ts4.isMethodDeclaration(member)) {
1882
+ } else if (ts3.isMethodDeclaration(member)) {
1753
1883
  const methodInfo = analyzeMethod(member, checker);
1754
1884
  if (methodInfo) {
1755
- const isStatic = member.modifiers?.some((m) => m.kind === ts4.SyntaxKind.StaticKeyword);
1885
+ const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
1756
1886
  if (isStatic) {
1757
1887
  staticMethods.push(methodInfo);
1758
1888
  } else {
@@ -1771,15 +1901,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1771
1901
  staticMethods
1772
1902
  };
1773
1903
  }
1774
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1904
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
1775
1905
  const name = interfaceDecl.name.text;
1776
1906
  const fields = [];
1777
1907
  const typeRegistry = {};
1778
- const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1908
+ const annotations = extractJSDocAnnotationNodes(
1909
+ interfaceDecl,
1910
+ file,
1911
+ makeParseOptions(extensionRegistry)
1912
+ );
1779
1913
  const visiting = /* @__PURE__ */ new Set();
1780
1914
  for (const member of interfaceDecl.members) {
1781
- if (ts4.isPropertySignature(member)) {
1782
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1915
+ if (ts3.isPropertySignature(member)) {
1916
+ const fieldNode = analyzeInterfacePropertyToIR(
1917
+ member,
1918
+ checker,
1919
+ file,
1920
+ typeRegistry,
1921
+ visiting,
1922
+ extensionRegistry
1923
+ );
1783
1924
  if (fieldNode) {
1784
1925
  fields.push(fieldNode);
1785
1926
  }
@@ -1796,11 +1937,11 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1796
1937
  staticMethods: []
1797
1938
  };
1798
1939
  }
1799
- function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1800
- if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1940
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1941
+ if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1801
1942
  const sourceFile = typeAlias.getSourceFile();
1802
1943
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
1803
- const kindDesc = ts4.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1944
+ const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1804
1945
  return {
1805
1946
  ok: false,
1806
1947
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
@@ -1809,11 +1950,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1809
1950
  const name = typeAlias.name.text;
1810
1951
  const fields = [];
1811
1952
  const typeRegistry = {};
1812
- const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1953
+ const annotations = extractJSDocAnnotationNodes(
1954
+ typeAlias,
1955
+ file,
1956
+ makeParseOptions(extensionRegistry)
1957
+ );
1813
1958
  const visiting = /* @__PURE__ */ new Set();
1814
1959
  for (const member of typeAlias.type.members) {
1815
- if (ts4.isPropertySignature(member)) {
1816
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1960
+ if (ts3.isPropertySignature(member)) {
1961
+ const fieldNode = analyzeInterfacePropertyToIR(
1962
+ member,
1963
+ checker,
1964
+ file,
1965
+ typeRegistry,
1966
+ visiting,
1967
+ extensionRegistry
1968
+ );
1817
1969
  if (fieldNode) {
1818
1970
  fields.push(fieldNode);
1819
1971
  }
@@ -1832,22 +1984,36 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1832
1984
  }
1833
1985
  };
1834
1986
  }
1835
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1836
- if (!ts4.isIdentifier(prop.name)) {
1987
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1988
+ if (!ts3.isIdentifier(prop.name)) {
1837
1989
  return null;
1838
1990
  }
1839
1991
  const name = prop.name.text;
1840
1992
  const tsType = checker.getTypeAtLocation(prop);
1841
1993
  const optional = prop.questionToken !== void 0;
1842
1994
  const provenance = provenanceForNode(prop, file);
1843
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1995
+ let type = resolveTypeNode(
1996
+ tsType,
1997
+ checker,
1998
+ file,
1999
+ typeRegistry,
2000
+ visiting,
2001
+ prop,
2002
+ extensionRegistry
2003
+ );
1844
2004
  const constraints = [];
1845
- if (prop.type) {
1846
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2005
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
2006
+ constraints.push(
2007
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2008
+ );
1847
2009
  }
1848
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2010
+ constraints.push(
2011
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
2012
+ );
1849
2013
  let annotations = [];
1850
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2014
+ annotations.push(
2015
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2016
+ );
1851
2017
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1852
2018
  if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1853
2019
  annotations.push(defaultAnnotation);
@@ -1863,22 +2029,36 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1863
2029
  provenance
1864
2030
  };
1865
2031
  }
1866
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
1867
- if (!ts4.isIdentifier(prop.name)) {
2032
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
2033
+ if (!ts3.isIdentifier(prop.name)) {
1868
2034
  return null;
1869
2035
  }
1870
2036
  const name = prop.name.text;
1871
2037
  const tsType = checker.getTypeAtLocation(prop);
1872
2038
  const optional = prop.questionToken !== void 0;
1873
2039
  const provenance = provenanceForNode(prop, file);
1874
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
2040
+ let type = resolveTypeNode(
2041
+ tsType,
2042
+ checker,
2043
+ file,
2044
+ typeRegistry,
2045
+ visiting,
2046
+ prop,
2047
+ extensionRegistry
2048
+ );
1875
2049
  const constraints = [];
1876
- if (prop.type) {
1877
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
2050
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
2051
+ constraints.push(
2052
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2053
+ );
1878
2054
  }
1879
- constraints.push(...extractJSDocConstraintNodes(prop, file));
2055
+ constraints.push(
2056
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
2057
+ );
1880
2058
  let annotations = [];
1881
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
2059
+ annotations.push(
2060
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
2061
+ );
1882
2062
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1883
2063
  return {
1884
2064
  kind: "field",
@@ -1952,20 +2132,94 @@ function parseEnumMemberDisplayName(value) {
1952
2132
  if (label === "") return null;
1953
2133
  return { value: match[1], label };
1954
2134
  }
1955
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1956
- if (type.flags & ts4.TypeFlags.String) {
2135
+ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
2136
+ if (sourceNode === void 0 || extensionRegistry === void 0) {
2137
+ return null;
2138
+ }
2139
+ const typeNode = extractTypeNodeFromSource(sourceNode);
2140
+ if (typeNode === void 0) {
2141
+ return null;
2142
+ }
2143
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
2144
+ }
2145
+ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
2146
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2147
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
2148
+ }
2149
+ const typeName = getTypeNodeRegistrationName(typeNode);
2150
+ if (typeName === null) {
2151
+ return null;
2152
+ }
2153
+ const registration = extensionRegistry.findTypeByName(typeName);
2154
+ if (registration !== void 0) {
2155
+ return {
2156
+ kind: "custom",
2157
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
2158
+ payload: null
2159
+ };
2160
+ }
2161
+ if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
2162
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2163
+ if (aliasDecl !== void 0) {
2164
+ return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
2165
+ }
2166
+ }
2167
+ return null;
2168
+ }
2169
+ function extractTypeNodeFromSource(sourceNode) {
2170
+ if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
2171
+ return sourceNode.type;
2172
+ }
2173
+ if (ts3.isTypeNode(sourceNode)) {
2174
+ return sourceNode;
2175
+ }
2176
+ return void 0;
2177
+ }
2178
+ function getTypeNodeRegistrationName(typeNode) {
2179
+ if (ts3.isTypeReferenceNode(typeNode)) {
2180
+ return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
2181
+ }
2182
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2183
+ return getTypeNodeRegistrationName(typeNode.type);
2184
+ }
2185
+ if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
2186
+ return typeNode.getText();
2187
+ }
2188
+ return null;
2189
+ }
2190
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2191
+ const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2192
+ if (customType) {
2193
+ return customType;
2194
+ }
2195
+ const primitiveAlias = tryResolveNamedPrimitiveAlias(
2196
+ type,
2197
+ checker,
2198
+ file,
2199
+ typeRegistry,
2200
+ visiting,
2201
+ sourceNode,
2202
+ extensionRegistry
2203
+ );
2204
+ if (primitiveAlias) {
2205
+ return primitiveAlias;
2206
+ }
2207
+ if (type.flags & ts3.TypeFlags.String) {
1957
2208
  return { kind: "primitive", primitiveKind: "string" };
1958
2209
  }
1959
- if (type.flags & ts4.TypeFlags.Number) {
2210
+ if (type.flags & ts3.TypeFlags.Number) {
1960
2211
  return { kind: "primitive", primitiveKind: "number" };
1961
2212
  }
1962
- if (type.flags & ts4.TypeFlags.Boolean) {
2213
+ if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
2214
+ return { kind: "primitive", primitiveKind: "bigint" };
2215
+ }
2216
+ if (type.flags & ts3.TypeFlags.Boolean) {
1963
2217
  return { kind: "primitive", primitiveKind: "boolean" };
1964
2218
  }
1965
- if (type.flags & ts4.TypeFlags.Null) {
2219
+ if (type.flags & ts3.TypeFlags.Null) {
1966
2220
  return { kind: "primitive", primitiveKind: "null" };
1967
2221
  }
1968
- if (type.flags & ts4.TypeFlags.Undefined) {
2222
+ if (type.flags & ts3.TypeFlags.Undefined) {
1969
2223
  return { kind: "primitive", primitiveKind: "null" };
1970
2224
  }
1971
2225
  if (type.isStringLiteral()) {
@@ -1981,27 +2235,120 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
1981
2235
  };
1982
2236
  }
1983
2237
  if (type.isUnion()) {
1984
- return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
2238
+ return resolveUnionType(
2239
+ type,
2240
+ checker,
2241
+ file,
2242
+ typeRegistry,
2243
+ visiting,
2244
+ sourceNode,
2245
+ extensionRegistry
2246
+ );
1985
2247
  }
1986
2248
  if (checker.isArrayType(type)) {
1987
- return resolveArrayType(type, checker, file, typeRegistry, visiting);
2249
+ return resolveArrayType(
2250
+ type,
2251
+ checker,
2252
+ file,
2253
+ typeRegistry,
2254
+ visiting,
2255
+ sourceNode,
2256
+ extensionRegistry
2257
+ );
1988
2258
  }
1989
2259
  if (isObjectType(type)) {
1990
- return resolveObjectType(type, checker, file, typeRegistry, visiting);
2260
+ return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
1991
2261
  }
1992
2262
  return { kind: "primitive", primitiveKind: "string" };
1993
2263
  }
1994
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
2264
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2265
+ if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2266
+ return null;
2267
+ }
2268
+ const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
2269
+ if (!aliasDecl) {
2270
+ return null;
2271
+ }
2272
+ const aliasName = aliasDecl.name.text;
2273
+ if (!typeRegistry[aliasName]) {
2274
+ const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
2275
+ const constraints = [
2276
+ ...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
2277
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
2278
+ ];
2279
+ const annotations = extractJSDocAnnotationNodes(
2280
+ aliasDecl,
2281
+ file,
2282
+ makeParseOptions(extensionRegistry)
2283
+ );
2284
+ typeRegistry[aliasName] = {
2285
+ name: aliasName,
2286
+ type: resolveAliasedPrimitiveTarget(
2287
+ aliasType,
2288
+ checker,
2289
+ file,
2290
+ typeRegistry,
2291
+ visiting,
2292
+ extensionRegistry
2293
+ ),
2294
+ ...constraints.length > 0 && { constraints },
2295
+ ...annotations.length > 0 && { annotations },
2296
+ provenance: provenanceForDeclaration(aliasDecl, file)
2297
+ };
2298
+ }
2299
+ return { kind: "reference", name: aliasName, typeArguments: [] };
2300
+ }
2301
+ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
2302
+ const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
2303
+ if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
2304
+ return void 0;
2305
+ }
2306
+ return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2307
+ }
2308
+ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2309
+ if (!ts3.isTypeReferenceNode(typeNode)) {
2310
+ return false;
2311
+ }
2312
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2313
+ if (!aliasDecl) {
2314
+ return false;
2315
+ }
2316
+ const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2317
+ return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2318
+ }
2319
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2320
+ const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2321
+ if (nestedAliasDecl !== void 0) {
2322
+ return resolveAliasedPrimitiveTarget(
2323
+ checker.getTypeFromTypeNode(nestedAliasDecl.type),
2324
+ checker,
2325
+ file,
2326
+ typeRegistry,
2327
+ visiting,
2328
+ extensionRegistry
2329
+ );
2330
+ }
2331
+ return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
2332
+ }
2333
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1995
2334
  const typeName = getNamedTypeName(type);
1996
2335
  const namedDecl = getNamedTypeDeclaration(type);
1997
2336
  if (typeName && typeName in typeRegistry) {
1998
2337
  return { kind: "reference", name: typeName, typeArguments: [] };
1999
2338
  }
2000
2339
  const allTypes = type.types;
2340
+ const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
2341
+ const nonNullSourceNodes = unionMemberTypeNodes.filter(
2342
+ (memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
2343
+ );
2001
2344
  const nonNullTypes = allTypes.filter(
2002
- (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
2345
+ (memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
2003
2346
  );
2004
- const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2347
+ const nonNullMembers = nonNullTypes.map((memberType, index) => ({
2348
+ memberType,
2349
+ sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
2350
+ }));
2351
+ const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
2005
2352
  const memberDisplayNames = /* @__PURE__ */ new Map();
2006
2353
  if (namedDecl) {
2007
2354
  for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
@@ -2017,7 +2364,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2017
2364
  if (!typeName) {
2018
2365
  return result;
2019
2366
  }
2020
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2367
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2021
2368
  typeRegistry[typeName] = {
2022
2369
  name: typeName,
2023
2370
  type: result,
@@ -2030,7 +2377,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2030
2377
  const displayName = memberDisplayNames.get(String(value));
2031
2378
  return displayName !== void 0 ? { value, displayName } : { value };
2032
2379
  });
2033
- const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
2380
+ const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
2034
2381
  if (isBooleanUnion2) {
2035
2382
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
2036
2383
  const result = hasNull ? {
@@ -2065,14 +2412,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2065
2412
  } : enumNode;
2066
2413
  return registerNamed(result);
2067
2414
  }
2068
- if (nonNullTypes.length === 1 && nonNullTypes[0]) {
2415
+ if (nonNullMembers.length === 1 && nonNullMembers[0]) {
2069
2416
  const inner = resolveTypeNode(
2070
- nonNullTypes[0],
2417
+ nonNullMembers[0].memberType,
2071
2418
  checker,
2072
2419
  file,
2073
2420
  typeRegistry,
2074
2421
  visiting,
2075
- sourceNode
2422
+ nonNullMembers[0].sourceNode ?? sourceNode,
2423
+ extensionRegistry
2076
2424
  );
2077
2425
  const result = hasNull ? {
2078
2426
  kind: "union",
@@ -2080,29 +2428,54 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2080
2428
  } : inner;
2081
2429
  return registerNamed(result);
2082
2430
  }
2083
- const members = nonNullTypes.map(
2084
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
2431
+ const members = nonNullMembers.map(
2432
+ ({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
2433
+ memberType,
2434
+ checker,
2435
+ file,
2436
+ typeRegistry,
2437
+ visiting,
2438
+ memberSourceNode ?? sourceNode,
2439
+ extensionRegistry
2440
+ )
2085
2441
  );
2086
2442
  if (hasNull) {
2087
2443
  members.push({ kind: "primitive", primitiveKind: "null" });
2088
2444
  }
2089
2445
  return registerNamed({ kind: "union", members });
2090
2446
  }
2091
- function resolveArrayType(type, checker, file, typeRegistry, visiting) {
2447
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2092
2448
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2093
2449
  const elementType = typeArgs?.[0];
2094
- const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
2450
+ const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
2451
+ const items = elementType ? resolveTypeNode(
2452
+ elementType,
2453
+ checker,
2454
+ file,
2455
+ typeRegistry,
2456
+ visiting,
2457
+ elementSourceNode,
2458
+ extensionRegistry
2459
+ ) : { kind: "primitive", primitiveKind: "string" };
2095
2460
  return { kind: "array", items };
2096
2461
  }
2097
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2462
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2098
2463
  if (type.getProperties().length > 0) {
2099
2464
  return null;
2100
2465
  }
2101
- const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
2466
+ const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
2102
2467
  if (!indexInfo) {
2103
2468
  return null;
2104
2469
  }
2105
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2470
+ const valueType = resolveTypeNode(
2471
+ indexInfo.type,
2472
+ checker,
2473
+ file,
2474
+ typeRegistry,
2475
+ visiting,
2476
+ void 0,
2477
+ extensionRegistry
2478
+ );
2106
2479
  return { kind: "record", valueType };
2107
2480
  }
2108
2481
  function typeNodeContainsReference(type, targetName) {
@@ -2130,7 +2503,7 @@ function typeNodeContainsReference(type, targetName) {
2130
2503
  }
2131
2504
  }
2132
2505
  }
2133
- function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2506
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2134
2507
  const typeName = getNamedTypeName(type);
2135
2508
  const namedTypeName = typeName ?? void 0;
2136
2509
  const namedDecl = getNamedTypeDeclaration(type);
@@ -2161,7 +2534,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2161
2534
  return { kind: "reference", name: namedTypeName, typeArguments: [] };
2162
2535
  }
2163
2536
  }
2164
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2537
+ const recordNode = tryResolveRecordType(
2538
+ type,
2539
+ checker,
2540
+ file,
2541
+ typeRegistry,
2542
+ visiting,
2543
+ extensionRegistry
2544
+ );
2165
2545
  if (recordNode) {
2166
2546
  visiting.delete(type);
2167
2547
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
@@ -2170,7 +2550,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2170
2550
  clearNamedTypeRegistration();
2171
2551
  return recordNode;
2172
2552
  }
2173
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2553
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2174
2554
  typeRegistry[namedTypeName] = {
2175
2555
  name: namedTypeName,
2176
2556
  type: recordNode,
@@ -2182,19 +2562,27 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2182
2562
  return recordNode;
2183
2563
  }
2184
2564
  const properties = [];
2185
- const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
2565
+ const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
2566
+ type,
2567
+ checker,
2568
+ file,
2569
+ typeRegistry,
2570
+ visiting,
2571
+ extensionRegistry
2572
+ );
2186
2573
  for (const prop of type.getProperties()) {
2187
2574
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2188
2575
  if (!declaration) continue;
2189
2576
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
2190
- const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
2577
+ const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
2191
2578
  const propTypeNode = resolveTypeNode(
2192
2579
  propType,
2193
2580
  checker,
2194
2581
  file,
2195
2582
  typeRegistry,
2196
2583
  visiting,
2197
- declaration
2584
+ declaration,
2585
+ extensionRegistry
2198
2586
  );
2199
2587
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2200
2588
  properties.push({
@@ -2213,7 +2601,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2213
2601
  additionalProperties: true
2214
2602
  };
2215
2603
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2216
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2604
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2217
2605
  typeRegistry[namedTypeName] = {
2218
2606
  name: namedTypeName,
2219
2607
  type: objectNode,
@@ -2224,19 +2612,26 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2224
2612
  }
2225
2613
  return objectNode;
2226
2614
  }
2227
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
2615
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2228
2616
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
2229
2617
  (s) => s?.declarations != null && s.declarations.length > 0
2230
2618
  );
2231
2619
  for (const symbol of symbols) {
2232
2620
  const declarations = symbol.declarations;
2233
2621
  if (!declarations) continue;
2234
- const classDecl = declarations.find(ts4.isClassDeclaration);
2622
+ const classDecl = declarations.find(ts3.isClassDeclaration);
2235
2623
  if (classDecl) {
2236
2624
  const map = /* @__PURE__ */ new Map();
2237
2625
  for (const member of classDecl.members) {
2238
- if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
2239
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
2626
+ if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
2627
+ const fieldNode = analyzeFieldToIR(
2628
+ member,
2629
+ checker,
2630
+ file,
2631
+ typeRegistry,
2632
+ visiting,
2633
+ extensionRegistry
2634
+ );
2240
2635
  if (fieldNode) {
2241
2636
  map.set(fieldNode.name, {
2242
2637
  constraints: [...fieldNode.constraints],
@@ -2248,28 +2643,86 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2248
2643
  }
2249
2644
  return map;
2250
2645
  }
2251
- const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
2646
+ const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
2252
2647
  if (interfaceDecl) {
2253
- return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
2254
- }
2255
- const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
2256
- if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
2648
+ return buildFieldNodeInfoMap(
2649
+ interfaceDecl.members,
2650
+ checker,
2651
+ file,
2652
+ typeRegistry,
2653
+ visiting,
2654
+ extensionRegistry
2655
+ );
2656
+ }
2657
+ const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
2658
+ if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
2257
2659
  return buildFieldNodeInfoMap(
2258
2660
  typeAliasDecl.type.members,
2259
2661
  checker,
2260
2662
  file,
2261
2663
  typeRegistry,
2262
- visiting
2664
+ visiting,
2665
+ extensionRegistry
2263
2666
  );
2264
2667
  }
2265
2668
  }
2266
2669
  return null;
2267
2670
  }
2268
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2671
+ function extractArrayElementTypeNode(sourceNode, checker) {
2672
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2673
+ if (typeNode === void 0) {
2674
+ return void 0;
2675
+ }
2676
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2677
+ if (ts3.isArrayTypeNode(resolvedTypeNode)) {
2678
+ return resolvedTypeNode.elementType;
2679
+ }
2680
+ if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
2681
+ return resolvedTypeNode.typeArguments[0];
2682
+ }
2683
+ return void 0;
2684
+ }
2685
+ function extractUnionMemberTypeNodes(sourceNode, checker) {
2686
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2687
+ if (!typeNode) {
2688
+ return [];
2689
+ }
2690
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2691
+ return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
2692
+ }
2693
+ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
2694
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2695
+ return resolveAliasedTypeNode(typeNode.type, checker, visited);
2696
+ }
2697
+ if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
2698
+ return typeNode;
2699
+ }
2700
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2701
+ const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2702
+ if (aliasDecl === void 0 || visited.has(aliasDecl)) {
2703
+ return typeNode;
2704
+ }
2705
+ visited.add(aliasDecl);
2706
+ return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
2707
+ }
2708
+ function isNullishTypeNode(typeNode) {
2709
+ if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
2710
+ return true;
2711
+ }
2712
+ return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
2713
+ }
2714
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
2269
2715
  const map = /* @__PURE__ */ new Map();
2270
2716
  for (const member of members) {
2271
- if (ts4.isPropertySignature(member)) {
2272
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
2717
+ if (ts3.isPropertySignature(member)) {
2718
+ const fieldNode = analyzeInterfacePropertyToIR(
2719
+ member,
2720
+ checker,
2721
+ file,
2722
+ typeRegistry,
2723
+ visiting,
2724
+ extensionRegistry
2725
+ );
2273
2726
  if (fieldNode) {
2274
2727
  map.set(fieldNode.name, {
2275
2728
  constraints: [...fieldNode.constraints],
@@ -2282,8 +2735,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
2282
2735
  return map;
2283
2736
  }
2284
2737
  var MAX_ALIAS_CHAIN_DEPTH = 8;
2285
- function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2286
- if (!ts4.isTypeReferenceNode(typeNode)) return [];
2738
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
2739
+ if (!ts3.isTypeReferenceNode(typeNode)) return [];
2287
2740
  if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
2288
2741
  const aliasName = typeNode.typeName.getText();
2289
2742
  throw new Error(
@@ -2292,11 +2745,26 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
2292
2745
  }
2293
2746
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2294
2747
  if (!symbol?.declarations) return [];
2295
- const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
2748
+ const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
2296
2749
  if (!aliasDecl) return [];
2297
- if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
2298
- const constraints = extractJSDocConstraintNodes(aliasDecl, file);
2299
- constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
2750
+ if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
2751
+ const aliasFieldType = resolveTypeNode(
2752
+ checker.getTypeAtLocation(aliasDecl.type),
2753
+ checker,
2754
+ file,
2755
+ {},
2756
+ /* @__PURE__ */ new Set(),
2757
+ aliasDecl.type,
2758
+ extensionRegistry
2759
+ );
2760
+ const constraints = extractJSDocConstraintNodes(
2761
+ aliasDecl,
2762
+ file,
2763
+ makeParseOptions(extensionRegistry, aliasFieldType)
2764
+ );
2765
+ constraints.push(
2766
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
2767
+ );
2300
2768
  return constraints;
2301
2769
  }
2302
2770
  function provenanceForNode(node, file) {
@@ -2322,14 +2790,14 @@ function getNamedTypeName(type) {
2322
2790
  const symbol = type.getSymbol();
2323
2791
  if (symbol?.declarations) {
2324
2792
  const decl = symbol.declarations[0];
2325
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2326
- const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
2793
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
2794
+ const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
2327
2795
  if (name) return name;
2328
2796
  }
2329
2797
  }
2330
2798
  const aliasSymbol = type.aliasSymbol;
2331
2799
  if (aliasSymbol?.declarations) {
2332
- const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2800
+ const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
2333
2801
  if (aliasDecl) {
2334
2802
  return aliasDecl.name.text;
2335
2803
  }
@@ -2340,24 +2808,24 @@ function getNamedTypeDeclaration(type) {
2340
2808
  const symbol = type.getSymbol();
2341
2809
  if (symbol?.declarations) {
2342
2810
  const decl = symbol.declarations[0];
2343
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2811
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
2344
2812
  return decl;
2345
2813
  }
2346
2814
  }
2347
2815
  const aliasSymbol = type.aliasSymbol;
2348
2816
  if (aliasSymbol?.declarations) {
2349
- return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2817
+ return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
2350
2818
  }
2351
2819
  return void 0;
2352
2820
  }
2353
2821
  function analyzeMethod(method, checker) {
2354
- if (!ts4.isIdentifier(method.name)) {
2822
+ if (!ts3.isIdentifier(method.name)) {
2355
2823
  return null;
2356
2824
  }
2357
2825
  const name = method.name.text;
2358
2826
  const parameters = [];
2359
2827
  for (const param of method.parameters) {
2360
- if (ts4.isIdentifier(param.name)) {
2828
+ if (ts3.isIdentifier(param.name)) {
2361
2829
  const paramInfo = analyzeParameter(param, checker);
2362
2830
  parameters.push(paramInfo);
2363
2831
  }
@@ -2368,7 +2836,7 @@ function analyzeMethod(method, checker) {
2368
2836
  return { name, parameters, returnTypeNode, returnType };
2369
2837
  }
2370
2838
  function analyzeParameter(param, checker) {
2371
- const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
2839
+ const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
2372
2840
  const typeNode = param.type;
2373
2841
  const type = checker.getTypeAtLocation(param);
2374
2842
  const formSpecExportName = detectFormSpecReference(typeNode);
@@ -2377,67 +2845,966 @@ function analyzeParameter(param, checker) {
2377
2845
  }
2378
2846
  function detectFormSpecReference(typeNode) {
2379
2847
  if (!typeNode) return null;
2380
- if (!ts4.isTypeReferenceNode(typeNode)) return null;
2381
- const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
2848
+ if (!ts3.isTypeReferenceNode(typeNode)) return null;
2849
+ const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
2382
2850
  if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
2383
2851
  const typeArg = typeNode.typeArguments?.[0];
2384
- if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
2385
- if (ts4.isIdentifier(typeArg.exprName)) {
2852
+ if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
2853
+ if (ts3.isIdentifier(typeArg.exprName)) {
2386
2854
  return typeArg.exprName.text;
2387
2855
  }
2388
- if (ts4.isQualifiedName(typeArg.exprName)) {
2856
+ if (ts3.isQualifiedName(typeArg.exprName)) {
2389
2857
  return typeArg.exprName.right.text;
2390
2858
  }
2391
2859
  return null;
2392
2860
  }
2393
2861
 
2862
+ // src/analyzer/program.ts
2863
+ function createProgramContext(filePath) {
2864
+ const absolutePath = path.resolve(filePath);
2865
+ const fileDir = path.dirname(absolutePath);
2866
+ const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
2867
+ let compilerOptions;
2868
+ let fileNames;
2869
+ if (configPath) {
2870
+ const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
2871
+ if (configFile.error) {
2872
+ throw new Error(
2873
+ `Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
2874
+ );
2875
+ }
2876
+ const parsed = ts4.parseJsonConfigFileContent(
2877
+ configFile.config,
2878
+ ts4.sys,
2879
+ path.dirname(configPath)
2880
+ );
2881
+ if (parsed.errors.length > 0) {
2882
+ const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
2883
+ throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
2884
+ }
2885
+ compilerOptions = parsed.options;
2886
+ fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
2887
+ } else {
2888
+ compilerOptions = {
2889
+ target: ts4.ScriptTarget.ES2022,
2890
+ module: ts4.ModuleKind.NodeNext,
2891
+ moduleResolution: ts4.ModuleResolutionKind.NodeNext,
2892
+ strict: true,
2893
+ skipLibCheck: true,
2894
+ declaration: true
2895
+ };
2896
+ fileNames = [absolutePath];
2897
+ }
2898
+ const program = ts4.createProgram(fileNames, compilerOptions);
2899
+ const sourceFile = program.getSourceFile(absolutePath);
2900
+ if (!sourceFile) {
2901
+ throw new Error(`Could not find source file: ${absolutePath}`);
2902
+ }
2903
+ return {
2904
+ program,
2905
+ checker: program.getTypeChecker(),
2906
+ sourceFile
2907
+ };
2908
+ }
2909
+ function findNodeByName(sourceFile, name, predicate, getName) {
2910
+ let result = null;
2911
+ function visit(node) {
2912
+ if (result) return;
2913
+ if (predicate(node) && getName(node) === name) {
2914
+ result = node;
2915
+ return;
2916
+ }
2917
+ ts4.forEachChild(node, visit);
2918
+ }
2919
+ visit(sourceFile);
2920
+ return result;
2921
+ }
2922
+ function findClassByName(sourceFile, className) {
2923
+ return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
2924
+ }
2925
+ function findInterfaceByName(sourceFile, interfaceName) {
2926
+ return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
2927
+ }
2928
+ function findTypeAliasByName(sourceFile, aliasName) {
2929
+ return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
2930
+ }
2931
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
2932
+ const ctx = createProgramContext(filePath);
2933
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2934
+ if (classDecl !== null) {
2935
+ return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
2936
+ }
2937
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2938
+ if (interfaceDecl !== null) {
2939
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
2940
+ }
2941
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2942
+ if (typeAlias !== null) {
2943
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
2944
+ if (result.ok) {
2945
+ return result.analysis;
2946
+ }
2947
+ throw new Error(result.error);
2948
+ }
2949
+ throw new Error(
2950
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2951
+ );
2952
+ }
2953
+
2954
+ // src/validate/constraint-validator.ts
2955
+ var import_core4 = require("@formspec/core");
2956
+ function addContradiction(ctx, message, primary, related) {
2957
+ ctx.diagnostics.push({
2958
+ code: "CONTRADICTING_CONSTRAINTS",
2959
+ message,
2960
+ severity: "error",
2961
+ primaryLocation: primary,
2962
+ relatedLocations: [related]
2963
+ });
2964
+ }
2965
+ function addTypeMismatch(ctx, message, primary) {
2966
+ ctx.diagnostics.push({
2967
+ code: "TYPE_MISMATCH",
2968
+ message,
2969
+ severity: "error",
2970
+ primaryLocation: primary,
2971
+ relatedLocations: []
2972
+ });
2973
+ }
2974
+ function addUnknownExtension(ctx, message, primary) {
2975
+ ctx.diagnostics.push({
2976
+ code: "UNKNOWN_EXTENSION",
2977
+ message,
2978
+ severity: "warning",
2979
+ primaryLocation: primary,
2980
+ relatedLocations: []
2981
+ });
2982
+ }
2983
+ function addUnknownPathTarget(ctx, message, primary) {
2984
+ ctx.diagnostics.push({
2985
+ code: "UNKNOWN_PATH_TARGET",
2986
+ message,
2987
+ severity: "error",
2988
+ primaryLocation: primary,
2989
+ relatedLocations: []
2990
+ });
2991
+ }
2992
+ function addConstraintBroadening(ctx, message, primary, related) {
2993
+ ctx.diagnostics.push({
2994
+ code: "CONSTRAINT_BROADENING",
2995
+ message,
2996
+ severity: "error",
2997
+ primaryLocation: primary,
2998
+ relatedLocations: [related]
2999
+ });
3000
+ }
3001
+ function getExtensionIdFromConstraintId(constraintId) {
3002
+ const separator = constraintId.lastIndexOf("/");
3003
+ if (separator <= 0) {
3004
+ return null;
3005
+ }
3006
+ return constraintId.slice(0, separator);
3007
+ }
3008
+ function findNumeric(constraints, constraintKind) {
3009
+ return constraints.find((c) => c.constraintKind === constraintKind);
3010
+ }
3011
+ function findLength(constraints, constraintKind) {
3012
+ return constraints.find((c) => c.constraintKind === constraintKind);
3013
+ }
3014
+ function findAllowedMembers(constraints) {
3015
+ return constraints.filter(
3016
+ (c) => c.constraintKind === "allowedMembers"
3017
+ );
3018
+ }
3019
+ function findConstConstraints(constraints) {
3020
+ return constraints.filter(
3021
+ (c) => c.constraintKind === "const"
3022
+ );
3023
+ }
3024
+ function jsonValueEquals(left, right) {
3025
+ if (left === right) {
3026
+ return true;
3027
+ }
3028
+ if (Array.isArray(left) || Array.isArray(right)) {
3029
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
3030
+ return false;
3031
+ }
3032
+ return left.every((item, index) => jsonValueEquals(item, right[index]));
3033
+ }
3034
+ if (isJsonObject(left) || isJsonObject(right)) {
3035
+ if (!isJsonObject(left) || !isJsonObject(right)) {
3036
+ return false;
3037
+ }
3038
+ const leftKeys = Object.keys(left).sort();
3039
+ const rightKeys = Object.keys(right).sort();
3040
+ if (leftKeys.length !== rightKeys.length) {
3041
+ return false;
3042
+ }
3043
+ return leftKeys.every((key, index) => {
3044
+ const rightKey = rightKeys[index];
3045
+ if (rightKey !== key) {
3046
+ return false;
3047
+ }
3048
+ const leftValue = left[key];
3049
+ const rightValue = right[rightKey];
3050
+ return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
3051
+ });
3052
+ }
3053
+ return false;
3054
+ }
3055
+ function isJsonObject(value) {
3056
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3057
+ }
3058
+ function isOrderedBoundConstraint(constraint) {
3059
+ 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";
3060
+ }
3061
+ function pathKey(constraint) {
3062
+ return constraint.path?.segments.join(".") ?? "";
3063
+ }
3064
+ function orderedBoundFamily(kind) {
3065
+ switch (kind) {
3066
+ case "minimum":
3067
+ case "exclusiveMinimum":
3068
+ return "numeric-lower";
3069
+ case "maximum":
3070
+ case "exclusiveMaximum":
3071
+ return "numeric-upper";
3072
+ case "minLength":
3073
+ return "minLength";
3074
+ case "minItems":
3075
+ return "minItems";
3076
+ case "maxLength":
3077
+ return "maxLength";
3078
+ case "maxItems":
3079
+ return "maxItems";
3080
+ default: {
3081
+ const _exhaustive = kind;
3082
+ return _exhaustive;
3083
+ }
3084
+ }
3085
+ }
3086
+ function isNumericLowerKind(kind) {
3087
+ return kind === "minimum" || kind === "exclusiveMinimum";
3088
+ }
3089
+ function isNumericUpperKind(kind) {
3090
+ return kind === "maximum" || kind === "exclusiveMaximum";
3091
+ }
3092
+ function describeConstraintTag(constraint) {
3093
+ return `@${constraint.constraintKind}`;
3094
+ }
3095
+ function compareConstraintStrength(current, previous) {
3096
+ const family = orderedBoundFamily(current.constraintKind);
3097
+ if (family === "numeric-lower") {
3098
+ if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
3099
+ throw new Error("numeric-lower family received non-numeric lower-bound constraint");
3100
+ }
3101
+ if (current.value !== previous.value) {
3102
+ return current.value > previous.value ? 1 : -1;
3103
+ }
3104
+ if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
3105
+ return 1;
3106
+ }
3107
+ if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
3108
+ return -1;
3109
+ }
3110
+ return 0;
3111
+ }
3112
+ if (family === "numeric-upper") {
3113
+ if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
3114
+ throw new Error("numeric-upper family received non-numeric upper-bound constraint");
3115
+ }
3116
+ if (current.value !== previous.value) {
3117
+ return current.value < previous.value ? 1 : -1;
3118
+ }
3119
+ if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
3120
+ return 1;
3121
+ }
3122
+ if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
3123
+ return -1;
3124
+ }
3125
+ return 0;
3126
+ }
3127
+ switch (family) {
3128
+ case "minLength":
3129
+ case "minItems":
3130
+ if (current.value === previous.value) {
3131
+ return 0;
3132
+ }
3133
+ return current.value > previous.value ? 1 : -1;
3134
+ case "maxLength":
3135
+ case "maxItems":
3136
+ if (current.value === previous.value) {
3137
+ return 0;
3138
+ }
3139
+ return current.value < previous.value ? 1 : -1;
3140
+ default: {
3141
+ const _exhaustive = family;
3142
+ return _exhaustive;
3143
+ }
3144
+ }
3145
+ }
3146
+ function checkConstraintBroadening(ctx, fieldName, constraints) {
3147
+ const strongestByKey = /* @__PURE__ */ new Map();
3148
+ for (const constraint of constraints) {
3149
+ if (!isOrderedBoundConstraint(constraint)) {
3150
+ continue;
3151
+ }
3152
+ const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
3153
+ const previous = strongestByKey.get(key);
3154
+ if (previous === void 0) {
3155
+ strongestByKey.set(key, constraint);
3156
+ continue;
3157
+ }
3158
+ const strength = compareConstraintStrength(constraint, previous);
3159
+ if (strength < 0) {
3160
+ const displayFieldName = formatPathTargetFieldName(
3161
+ fieldName,
3162
+ constraint.path?.segments ?? []
3163
+ );
3164
+ addConstraintBroadening(
3165
+ ctx,
3166
+ `Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
3167
+ constraint.provenance,
3168
+ previous.provenance
3169
+ );
3170
+ continue;
3171
+ }
3172
+ if (strength <= 0) {
3173
+ continue;
3174
+ }
3175
+ strongestByKey.set(key, constraint);
3176
+ }
3177
+ }
3178
+ function compareCustomConstraintStrength(current, previous) {
3179
+ const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
3180
+ const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
3181
+ switch (current.role.bound) {
3182
+ case "lower":
3183
+ return equalPayloadTiebreaker;
3184
+ case "upper":
3185
+ return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
3186
+ case "exact":
3187
+ return order === 0 ? 0 : Number.NaN;
3188
+ default: {
3189
+ const _exhaustive = current.role.bound;
3190
+ return _exhaustive;
3191
+ }
3192
+ }
3193
+ }
3194
+ function compareSemanticInclusivity(currentInclusive, previousInclusive) {
3195
+ if (currentInclusive === previousInclusive) {
3196
+ return 0;
3197
+ }
3198
+ return currentInclusive ? -1 : 1;
3199
+ }
3200
+ function customConstraintsContradict(lower, upper) {
3201
+ const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
3202
+ if (order > 0) {
3203
+ return true;
3204
+ }
3205
+ if (order < 0) {
3206
+ return false;
3207
+ }
3208
+ return !lower.role.inclusive || !upper.role.inclusive;
3209
+ }
3210
+ function describeCustomConstraintTag(constraint) {
3211
+ return constraint.provenance.tagName ?? constraint.constraintId;
3212
+ }
3213
+ function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
3214
+ if (ctx.extensionRegistry === void 0) {
3215
+ return;
3216
+ }
3217
+ const strongestByKey = /* @__PURE__ */ new Map();
3218
+ const lowerByFamily = /* @__PURE__ */ new Map();
3219
+ const upperByFamily = /* @__PURE__ */ new Map();
3220
+ for (const constraint of constraints) {
3221
+ if (constraint.constraintKind !== "custom") {
3222
+ continue;
3223
+ }
3224
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3225
+ if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
3226
+ continue;
3227
+ }
3228
+ const entry = {
3229
+ constraint,
3230
+ comparePayloads: registration.comparePayloads,
3231
+ role: registration.semanticRole
3232
+ };
3233
+ const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
3234
+ const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
3235
+ const previous = strongestByKey.get(boundKey);
3236
+ if (previous !== void 0) {
3237
+ const strength = compareCustomConstraintStrength(entry, previous);
3238
+ if (Number.isNaN(strength)) {
3239
+ addContradiction(
3240
+ ctx,
3241
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
3242
+ constraint.provenance,
3243
+ previous.constraint.provenance
3244
+ );
3245
+ continue;
3246
+ }
3247
+ if (strength < 0) {
3248
+ addConstraintBroadening(
3249
+ ctx,
3250
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
3251
+ constraint.provenance,
3252
+ previous.constraint.provenance
3253
+ );
3254
+ continue;
3255
+ }
3256
+ if (strength > 0) {
3257
+ strongestByKey.set(boundKey, entry);
3258
+ }
3259
+ } else {
3260
+ strongestByKey.set(boundKey, entry);
3261
+ }
3262
+ if (registration.semanticRole.bound === "lower") {
3263
+ lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3264
+ } else if (registration.semanticRole.bound === "upper") {
3265
+ upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3266
+ }
3267
+ }
3268
+ for (const [familyKey, lower] of lowerByFamily) {
3269
+ const upper = upperByFamily.get(familyKey);
3270
+ if (upper === void 0) {
3271
+ continue;
3272
+ }
3273
+ if (!customConstraintsContradict(lower, upper)) {
3274
+ continue;
3275
+ }
3276
+ addContradiction(
3277
+ ctx,
3278
+ `Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
3279
+ lower.constraint.provenance,
3280
+ upper.constraint.provenance
3281
+ );
3282
+ }
3283
+ }
3284
+ function checkNumericContradictions(ctx, fieldName, constraints) {
3285
+ const min = findNumeric(constraints, "minimum");
3286
+ const max = findNumeric(constraints, "maximum");
3287
+ const exMin = findNumeric(constraints, "exclusiveMinimum");
3288
+ const exMax = findNumeric(constraints, "exclusiveMaximum");
3289
+ if (min !== void 0 && max !== void 0 && min.value > max.value) {
3290
+ addContradiction(
3291
+ ctx,
3292
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
3293
+ min.provenance,
3294
+ max.provenance
3295
+ );
3296
+ }
3297
+ if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
3298
+ addContradiction(
3299
+ ctx,
3300
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
3301
+ exMin.provenance,
3302
+ max.provenance
3303
+ );
3304
+ }
3305
+ if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
3306
+ addContradiction(
3307
+ ctx,
3308
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3309
+ min.provenance,
3310
+ exMax.provenance
3311
+ );
3312
+ }
3313
+ if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
3314
+ addContradiction(
3315
+ ctx,
3316
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3317
+ exMin.provenance,
3318
+ exMax.provenance
3319
+ );
3320
+ }
3321
+ }
3322
+ function checkLengthContradictions(ctx, fieldName, constraints) {
3323
+ const minLen = findLength(constraints, "minLength");
3324
+ const maxLen = findLength(constraints, "maxLength");
3325
+ if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
3326
+ addContradiction(
3327
+ ctx,
3328
+ `Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
3329
+ minLen.provenance,
3330
+ maxLen.provenance
3331
+ );
3332
+ }
3333
+ const minItems = findLength(constraints, "minItems");
3334
+ const maxItems = findLength(constraints, "maxItems");
3335
+ if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
3336
+ addContradiction(
3337
+ ctx,
3338
+ `Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
3339
+ minItems.provenance,
3340
+ maxItems.provenance
3341
+ );
3342
+ }
3343
+ }
3344
+ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
3345
+ const members = findAllowedMembers(constraints);
3346
+ if (members.length < 2) return;
3347
+ const firstSet = new Set(members[0]?.members ?? []);
3348
+ for (let i = 1; i < members.length; i++) {
3349
+ const current = members[i];
3350
+ if (current === void 0) continue;
3351
+ for (const m of firstSet) {
3352
+ if (!current.members.includes(m)) {
3353
+ firstSet.delete(m);
3354
+ }
3355
+ }
3356
+ }
3357
+ if (firstSet.size === 0) {
3358
+ const first = members[0];
3359
+ const second = members[1];
3360
+ if (first !== void 0 && second !== void 0) {
3361
+ addContradiction(
3362
+ ctx,
3363
+ `Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
3364
+ first.provenance,
3365
+ second.provenance
3366
+ );
3367
+ }
3368
+ }
3369
+ }
3370
+ function checkConstContradictions(ctx, fieldName, constraints) {
3371
+ const constConstraints = findConstConstraints(constraints);
3372
+ if (constConstraints.length < 2) return;
3373
+ const first = constConstraints[0];
3374
+ if (first === void 0) return;
3375
+ for (let i = 1; i < constConstraints.length; i++) {
3376
+ const current = constConstraints[i];
3377
+ if (current === void 0) continue;
3378
+ if (jsonValueEquals(first.value, current.value)) {
3379
+ continue;
3380
+ }
3381
+ addContradiction(
3382
+ ctx,
3383
+ `Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
3384
+ first.provenance,
3385
+ current.provenance
3386
+ );
3387
+ }
3388
+ }
3389
+ function typeLabel(type) {
3390
+ switch (type.kind) {
3391
+ case "primitive":
3392
+ return type.primitiveKind;
3393
+ case "enum":
3394
+ return "enum";
3395
+ case "array":
3396
+ return "array";
3397
+ case "object":
3398
+ return "object";
3399
+ case "record":
3400
+ return "record";
3401
+ case "union":
3402
+ return "union";
3403
+ case "reference":
3404
+ return `reference(${type.name})`;
3405
+ case "dynamic":
3406
+ return `dynamic(${type.dynamicKind})`;
3407
+ case "custom":
3408
+ return `custom(${type.typeId})`;
3409
+ default: {
3410
+ const _exhaustive = type;
3411
+ return String(_exhaustive);
3412
+ }
3413
+ }
3414
+ }
3415
+ function dereferenceType(ctx, type) {
3416
+ let current = type;
3417
+ const seen = /* @__PURE__ */ new Set();
3418
+ while (current.kind === "reference") {
3419
+ if (seen.has(current.name)) {
3420
+ return current;
3421
+ }
3422
+ seen.add(current.name);
3423
+ const definition = ctx.typeRegistry[current.name];
3424
+ if (definition === void 0) {
3425
+ return current;
3426
+ }
3427
+ current = definition.type;
3428
+ }
3429
+ return current;
3430
+ }
3431
+ function collectReferencedTypeConstraints(ctx, type) {
3432
+ const collected = [];
3433
+ let current = type;
3434
+ const seen = /* @__PURE__ */ new Set();
3435
+ while (current.kind === "reference") {
3436
+ if (seen.has(current.name)) {
3437
+ break;
3438
+ }
3439
+ seen.add(current.name);
3440
+ const definition = ctx.typeRegistry[current.name];
3441
+ if (definition === void 0) {
3442
+ break;
3443
+ }
3444
+ if (definition.constraints !== void 0) {
3445
+ collected.push(...definition.constraints);
3446
+ }
3447
+ current = definition.type;
3448
+ }
3449
+ return collected;
3450
+ }
3451
+ function resolvePathTargetType(ctx, type, segments) {
3452
+ const effectiveType = dereferenceType(ctx, type);
3453
+ if (segments.length === 0) {
3454
+ return { kind: "resolved", type: effectiveType };
3455
+ }
3456
+ if (effectiveType.kind === "array") {
3457
+ return resolvePathTargetType(ctx, effectiveType.items, segments);
3458
+ }
3459
+ if (effectiveType.kind === "object") {
3460
+ const [segment, ...rest] = segments;
3461
+ if (segment === void 0) {
3462
+ throw new Error("Invariant violation: object path traversal requires a segment");
3463
+ }
3464
+ const property = effectiveType.properties.find((prop) => prop.name === segment);
3465
+ if (property === void 0) {
3466
+ return { kind: "missing-property", segment };
3467
+ }
3468
+ return resolvePathTargetType(ctx, property.type, rest);
3469
+ }
3470
+ return { kind: "unresolvable", type: effectiveType };
3471
+ }
3472
+ function isNullType(type) {
3473
+ return type.kind === "primitive" && type.primitiveKind === "null";
3474
+ }
3475
+ function collectCustomConstraintCandidateTypes(ctx, type) {
3476
+ const effectiveType = dereferenceType(ctx, type);
3477
+ const candidates = [effectiveType];
3478
+ if (effectiveType.kind === "array") {
3479
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
3480
+ }
3481
+ if (effectiveType.kind === "union") {
3482
+ const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
3483
+ const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
3484
+ if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
3485
+ const [nullableMember] = nonNullMembers;
3486
+ if (nullableMember !== void 0) {
3487
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
3488
+ }
3489
+ }
3490
+ }
3491
+ return candidates;
3492
+ }
3493
+ function formatPathTargetFieldName(fieldName, path3) {
3494
+ return path3.length === 0 ? fieldName : `${fieldName}.${path3.join(".")}`;
3495
+ }
3496
+ function checkConstraintOnType(ctx, fieldName, type, constraint) {
3497
+ const effectiveType = dereferenceType(ctx, type);
3498
+ const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
3499
+ const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
3500
+ const isArray = effectiveType.kind === "array";
3501
+ const isEnum = effectiveType.kind === "enum";
3502
+ const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
3503
+ const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
3504
+ const label = typeLabel(effectiveType);
3505
+ const ck = constraint.constraintKind;
3506
+ switch (ck) {
3507
+ case "minimum":
3508
+ case "maximum":
3509
+ case "exclusiveMinimum":
3510
+ case "exclusiveMaximum":
3511
+ case "multipleOf": {
3512
+ if (!isNumber) {
3513
+ addTypeMismatch(
3514
+ ctx,
3515
+ `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
3516
+ constraint.provenance
3517
+ );
3518
+ }
3519
+ break;
3520
+ }
3521
+ case "minLength":
3522
+ case "maxLength":
3523
+ case "pattern": {
3524
+ if (!isString && !isStringArray) {
3525
+ addTypeMismatch(
3526
+ ctx,
3527
+ `Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
3528
+ constraint.provenance
3529
+ );
3530
+ }
3531
+ break;
3532
+ }
3533
+ case "minItems":
3534
+ case "maxItems":
3535
+ case "uniqueItems": {
3536
+ if (!isArray) {
3537
+ addTypeMismatch(
3538
+ ctx,
3539
+ `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
3540
+ constraint.provenance
3541
+ );
3542
+ }
3543
+ break;
3544
+ }
3545
+ case "allowedMembers": {
3546
+ if (!isEnum) {
3547
+ addTypeMismatch(
3548
+ ctx,
3549
+ `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
3550
+ constraint.provenance
3551
+ );
3552
+ }
3553
+ break;
3554
+ }
3555
+ case "const": {
3556
+ const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
3557
+ effectiveType.primitiveKind
3558
+ ) || effectiveType.kind === "enum";
3559
+ if (!isPrimitiveConstType) {
3560
+ addTypeMismatch(
3561
+ ctx,
3562
+ `Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
3563
+ constraint.provenance
3564
+ );
3565
+ break;
3566
+ }
3567
+ if (effectiveType.kind === "primitive") {
3568
+ const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
3569
+ const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
3570
+ if (valueType !== expectedValueType) {
3571
+ addTypeMismatch(
3572
+ ctx,
3573
+ `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
3574
+ constraint.provenance
3575
+ );
3576
+ }
3577
+ break;
3578
+ }
3579
+ const memberValues = effectiveType.members.map((member) => member.value);
3580
+ if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
3581
+ addTypeMismatch(
3582
+ ctx,
3583
+ `Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
3584
+ constraint.provenance
3585
+ );
3586
+ }
3587
+ break;
3588
+ }
3589
+ case "custom": {
3590
+ checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
3591
+ break;
3592
+ }
3593
+ default: {
3594
+ const _exhaustive = constraint;
3595
+ throw new Error(
3596
+ `Unhandled constraint kind: ${_exhaustive.constraintKind}`
3597
+ );
3598
+ }
3599
+ }
3600
+ }
3601
+ function checkTypeApplicability(ctx, fieldName, type, constraints) {
3602
+ for (const constraint of constraints) {
3603
+ if (constraint.path) {
3604
+ const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
3605
+ const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
3606
+ if (resolution.kind === "missing-property") {
3607
+ addUnknownPathTarget(
3608
+ ctx,
3609
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
3610
+ constraint.provenance
3611
+ );
3612
+ continue;
3613
+ }
3614
+ if (resolution.kind === "unresolvable") {
3615
+ addTypeMismatch(
3616
+ ctx,
3617
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
3618
+ constraint.provenance
3619
+ );
3620
+ continue;
3621
+ }
3622
+ checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
3623
+ continue;
3624
+ }
3625
+ checkConstraintOnType(ctx, fieldName, type, constraint);
3626
+ }
3627
+ }
3628
+ function checkCustomConstraint(ctx, fieldName, type, constraint) {
3629
+ if (ctx.extensionRegistry === void 0) return;
3630
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3631
+ if (registration === void 0) {
3632
+ addUnknownExtension(
3633
+ ctx,
3634
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
3635
+ constraint.provenance
3636
+ );
3637
+ return;
3638
+ }
3639
+ const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
3640
+ const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core4.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
3641
+ if (normalizedTagName !== void 0) {
3642
+ const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
3643
+ const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
3644
+ if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
3645
+ (candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
3646
+ )) {
3647
+ addTypeMismatch(
3648
+ ctx,
3649
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3650
+ constraint.provenance
3651
+ );
3652
+ return;
3653
+ }
3654
+ }
3655
+ if (registration.applicableTypes === null) {
3656
+ if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
3657
+ addTypeMismatch(
3658
+ ctx,
3659
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3660
+ constraint.provenance
3661
+ );
3662
+ }
3663
+ return;
3664
+ }
3665
+ const applicableTypes = registration.applicableTypes;
3666
+ const matchesApplicableType = candidateTypes.some(
3667
+ (candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
3668
+ );
3669
+ if (!matchesApplicableType) {
3670
+ addTypeMismatch(
3671
+ ctx,
3672
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3673
+ constraint.provenance
3674
+ );
3675
+ }
3676
+ }
3677
+ function validateFieldNode(ctx, field) {
3678
+ validateConstraints(ctx, field.name, field.type, [
3679
+ ...collectReferencedTypeConstraints(ctx, field.type),
3680
+ ...field.constraints
3681
+ ]);
3682
+ if (field.type.kind === "object") {
3683
+ for (const prop of field.type.properties) {
3684
+ validateObjectProperty(ctx, field.name, prop);
3685
+ }
3686
+ }
3687
+ }
3688
+ function validateObjectProperty(ctx, parentName, prop) {
3689
+ const qualifiedName = `${parentName}.${prop.name}`;
3690
+ validateConstraints(ctx, qualifiedName, prop.type, [
3691
+ ...collectReferencedTypeConstraints(ctx, prop.type),
3692
+ ...prop.constraints
3693
+ ]);
3694
+ if (prop.type.kind === "object") {
3695
+ for (const nestedProp of prop.type.properties) {
3696
+ validateObjectProperty(ctx, qualifiedName, nestedProp);
3697
+ }
3698
+ }
3699
+ }
3700
+ function validateConstraints(ctx, name, type, constraints) {
3701
+ checkNumericContradictions(ctx, name, constraints);
3702
+ checkLengthContradictions(ctx, name, constraints);
3703
+ checkAllowedMembersContradiction(ctx, name, constraints);
3704
+ checkConstContradictions(ctx, name, constraints);
3705
+ checkConstraintBroadening(ctx, name, constraints);
3706
+ checkCustomConstraintSemantics(ctx, name, constraints);
3707
+ checkTypeApplicability(ctx, name, type, constraints);
3708
+ }
3709
+ function validateElement(ctx, element) {
3710
+ switch (element.kind) {
3711
+ case "field":
3712
+ validateFieldNode(ctx, element);
3713
+ break;
3714
+ case "group":
3715
+ for (const child of element.elements) {
3716
+ validateElement(ctx, child);
3717
+ }
3718
+ break;
3719
+ case "conditional":
3720
+ for (const child of element.elements) {
3721
+ validateElement(ctx, child);
3722
+ }
3723
+ break;
3724
+ default: {
3725
+ const _exhaustive = element;
3726
+ throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
3727
+ }
3728
+ }
3729
+ }
3730
+ function validateIR(ir, options) {
3731
+ const ctx = {
3732
+ diagnostics: [],
3733
+ extensionRegistry: options?.extensionRegistry,
3734
+ typeRegistry: ir.typeRegistry
3735
+ };
3736
+ for (const element of ir.elements) {
3737
+ validateElement(ctx, element);
3738
+ }
3739
+ return {
3740
+ diagnostics: ctx.diagnostics,
3741
+ valid: ctx.diagnostics.every((d) => d.severity !== "error")
3742
+ };
3743
+ }
3744
+
2394
3745
  // src/generators/class-schema.ts
2395
- function generateClassSchemas(analysis, source) {
3746
+ function generateClassSchemas(analysis, source, options) {
2396
3747
  const ir = canonicalizeTSDoc(analysis, source);
3748
+ const validationResult = validateIR(ir, {
3749
+ ...options?.extensionRegistry !== void 0 && {
3750
+ extensionRegistry: options.extensionRegistry
3751
+ },
3752
+ ...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
3753
+ });
3754
+ if (!validationResult.valid) {
3755
+ throw new Error(formatValidationError(validationResult.diagnostics));
3756
+ }
2397
3757
  return {
2398
- jsonSchema: generateJsonSchemaFromIR(ir),
3758
+ jsonSchema: generateJsonSchemaFromIR(ir, options),
2399
3759
  uiSchema: generateUiSchemaFromIR(ir)
2400
3760
  };
2401
3761
  }
3762
+ function formatValidationError(diagnostics) {
3763
+ const lines = diagnostics.map((diagnostic) => {
3764
+ const primary = formatLocation(diagnostic.primaryLocation);
3765
+ const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
3766
+ return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
3767
+ });
3768
+ return `FormSpec validation failed:
3769
+ ${lines.map((line) => `- ${line}`).join("\n")}`;
3770
+ }
3771
+ function formatLocation(location) {
3772
+ return `${location.file}:${String(location.line)}:${String(location.column)}`;
3773
+ }
2402
3774
  function generateSchemasFromClass(options) {
2403
3775
  const ctx = createProgramContext(options.filePath);
2404
3776
  const classDecl = findClassByName(ctx.sourceFile, options.className);
2405
3777
  if (!classDecl) {
2406
3778
  throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
2407
3779
  }
2408
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2409
- return generateClassSchemas(analysis, { file: options.filePath });
3780
+ const analysis = analyzeClassToIR(
3781
+ classDecl,
3782
+ ctx.checker,
3783
+ options.filePath,
3784
+ options.extensionRegistry
3785
+ );
3786
+ return generateClassSchemas(
3787
+ analysis,
3788
+ { file: options.filePath },
3789
+ {
3790
+ extensionRegistry: options.extensionRegistry,
3791
+ vendorPrefix: options.vendorPrefix
3792
+ }
3793
+ );
2410
3794
  }
2411
3795
  function generateSchemas(options) {
2412
- const ctx = createProgramContext(options.filePath);
2413
- const source = { file: options.filePath };
2414
- const classDecl = findClassByName(ctx.sourceFile, options.typeName);
2415
- if (classDecl) {
2416
- const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
2417
- return generateClassSchemas(analysis, source);
2418
- }
2419
- const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
2420
- if (interfaceDecl) {
2421
- const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
2422
- return generateClassSchemas(analysis, source);
2423
- }
2424
- const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
2425
- if (typeAlias) {
2426
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
2427
- if (result.ok) {
2428
- return generateClassSchemas(result.analysis, source);
2429
- }
2430
- throw new Error(result.error);
2431
- }
2432
- throw new Error(
2433
- `Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
3796
+ const analysis = analyzeNamedTypeToIR(
3797
+ options.filePath,
3798
+ options.typeName,
3799
+ options.extensionRegistry
2434
3800
  );
3801
+ return generateClassSchemas(analysis, { file: options.filePath }, options);
2435
3802
  }
2436
3803
 
2437
3804
  // src/generators/mixed-authoring.ts
2438
3805
  function buildMixedAuthoringSchemas(options) {
2439
3806
  const { filePath, typeName, overlays, ...schemaOptions } = options;
2440
- const analysis = analyzeNamedType(filePath, typeName);
3807
+ const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
2441
3808
  const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2442
3809
  const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2443
3810
  return {
@@ -2445,29 +3812,6 @@ function buildMixedAuthoringSchemas(options) {
2445
3812
  uiSchema: generateUiSchemaFromIR(ir)
2446
3813
  };
2447
3814
  }
2448
- function analyzeNamedType(filePath, typeName) {
2449
- const ctx = createProgramContext(filePath);
2450
- const source = { file: filePath };
2451
- const classDecl = findClassByName(ctx.sourceFile, typeName);
2452
- if (classDecl !== null) {
2453
- return analyzeClassToIR(classDecl, ctx.checker, source.file);
2454
- }
2455
- const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2456
- if (interfaceDecl !== null) {
2457
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2458
- }
2459
- const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2460
- if (typeAlias !== null) {
2461
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2462
- if (result.ok) {
2463
- return result.analysis;
2464
- }
2465
- throw new Error(result.error);
2466
- }
2467
- throw new Error(
2468
- `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2469
- );
2470
- }
2471
3815
  function composeAnalysisWithOverlays(analysis, overlays) {
2472
3816
  const overlayIR = canonicalizeChainDSL(overlays);
2473
3817
  const overlayFields = collectOverlayFields(overlayIR.elements);
@@ -2537,7 +3881,7 @@ function assertSupportedOverlayField(baseField, overlayField) {
2537
3881
  `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2538
3882
  );
2539
3883
  }
2540
- if (overlayField.required) {
3884
+ if (overlayField.required && !baseField.required) {
2541
3885
  throw new Error(
2542
3886
  `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2543
3887
  );
@@ -2627,7 +3971,7 @@ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2627
3971
  const overlayOnly = overlayAnnotations.filter(
2628
3972
  (annotation) => !baseKeys.has(annotationKey(annotation))
2629
3973
  );
2630
- return [...overlayOnly, ...baseAnnotations];
3974
+ return [...baseAnnotations, ...overlayOnly];
2631
3975
  }
2632
3976
  function annotationKey(annotation) {
2633
3977
  return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;