@formspec/build 0.1.0-alpha.17 → 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 (55) 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/extension-forms.d.ts +7 -0
  11. package/dist/__tests__/fixtures/extension-forms.d.ts.map +1 -0
  12. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -1
  13. package/dist/__tests__/fixtures/named-primitive-aliases.d.ts +15 -0
  14. package/dist/__tests__/fixtures/named-primitive-aliases.d.ts.map +1 -0
  15. package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts +14 -0
  16. package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts.map +1 -0
  17. package/dist/__tests__/fixtures/sample-forms.d.ts +10 -0
  18. package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -1
  19. package/dist/__tests__/generate-schemas.test.d.ts +2 -0
  20. package/dist/__tests__/generate-schemas.test.d.ts.map +1 -0
  21. package/dist/__tests__/parity/parity.test.d.ts +6 -2
  22. package/dist/__tests__/parity/parity.test.d.ts.map +1 -1
  23. package/dist/__tests__/parity/utils.d.ts +9 -4
  24. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  25. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  26. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  27. package/dist/analyzer/program.d.ts +15 -0
  28. package/dist/analyzer/program.d.ts.map +1 -1
  29. package/dist/analyzer/tsdoc-parser.d.ts +5 -0
  30. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  31. package/dist/browser.cjs +73 -10
  32. package/dist/browser.cjs.map +1 -1
  33. package/dist/browser.js +73 -10
  34. package/dist/browser.js.map +1 -1
  35. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  36. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  37. package/dist/cli.cjs +1147 -252
  38. package/dist/cli.cjs.map +1 -1
  39. package/dist/cli.js +1142 -248
  40. package/dist/cli.js.map +1 -1
  41. package/dist/extensions/registry.d.ts.map +1 -1
  42. package/dist/generators/class-schema.d.ts.map +1 -1
  43. package/dist/generators/method-schema.d.ts.map +1 -1
  44. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  45. package/dist/index.cjs +1121 -239
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.js +1121 -239
  48. package/dist/index.js.map +1 -1
  49. package/dist/internals.cjs +377 -195
  50. package/dist/internals.cjs.map +1 -1
  51. package/dist/internals.js +377 -195
  52. package/dist/internals.js.map +1 -1
  53. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  54. package/dist/validate/constraint-validator.d.ts.map +1 -1
  55. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ function canonicalizeChainDSL(form) {
20
20
  kind: "form-ir",
21
21
  irVersion: IR_VERSION,
22
22
  elements: canonicalizeElements(form.elements),
23
+ rootAnnotations: [],
23
24
  typeRegistry: {},
24
25
  provenance: CHAIN_DSL_PROVENANCE
25
26
  };
@@ -325,6 +326,7 @@ function canonicalizeTSDoc(analysis, source) {
325
326
  irVersion: IR_VERSION2,
326
327
  elements,
327
328
  typeRegistry: analysis.typeRegistry,
329
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
328
330
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
329
331
  provenance
330
332
  };
@@ -402,6 +404,9 @@ function generateJsonSchemaFromIR(ir, options) {
402
404
  const ctx = makeContext(options);
403
405
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
404
406
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
407
+ if (typeDef.constraints && typeDef.constraints.length > 0) {
408
+ applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
409
+ }
405
410
  if (typeDef.annotations && typeDef.annotations.length > 0) {
406
411
  applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
407
412
  }
@@ -570,7 +575,9 @@ function generateTypeNode(type, ctx) {
570
575
  }
571
576
  }
572
577
  function generatePrimitiveType(type) {
573
- return { type: type.primitiveKind };
578
+ return {
579
+ type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
580
+ };
574
581
  }
575
582
  function generateEnumType(type) {
576
583
  const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
@@ -743,7 +750,7 @@ function applyAnnotations(schema, annotations, ctx) {
743
750
  case "deprecated":
744
751
  schema.deprecated = true;
745
752
  if (annotation.message !== void 0 && annotation.message !== "") {
746
- schema["x-formspec-deprecation-description"] = annotation.message;
753
+ schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
747
754
  }
748
755
  break;
749
756
  case "placeholder":
@@ -1201,85 +1208,17 @@ var jsonSchema7Schema = z3.lazy(
1201
1208
  );
1202
1209
 
1203
1210
  // src/analyzer/program.ts
1204
- import * as ts from "typescript";
1211
+ import * as ts4 from "typescript";
1205
1212
  import * as path from "path";
1206
- function createProgramContext(filePath) {
1207
- const absolutePath = path.resolve(filePath);
1208
- const fileDir = path.dirname(absolutePath);
1209
- const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
1210
- let compilerOptions;
1211
- let fileNames;
1212
- if (configPath) {
1213
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
1214
- if (configFile.error) {
1215
- throw new Error(
1216
- `Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
1217
- );
1218
- }
1219
- const parsed = ts.parseJsonConfigFileContent(
1220
- configFile.config,
1221
- ts.sys,
1222
- path.dirname(configPath)
1223
- );
1224
- if (parsed.errors.length > 0) {
1225
- const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
1226
- throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
1227
- }
1228
- compilerOptions = parsed.options;
1229
- fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
1230
- } else {
1231
- compilerOptions = {
1232
- target: ts.ScriptTarget.ES2022,
1233
- module: ts.ModuleKind.NodeNext,
1234
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
1235
- strict: true,
1236
- skipLibCheck: true,
1237
- declaration: true
1238
- };
1239
- fileNames = [absolutePath];
1240
- }
1241
- const program = ts.createProgram(fileNames, compilerOptions);
1242
- const sourceFile = program.getSourceFile(absolutePath);
1243
- if (!sourceFile) {
1244
- throw new Error(`Could not find source file: ${absolutePath}`);
1245
- }
1246
- return {
1247
- program,
1248
- checker: program.getTypeChecker(),
1249
- sourceFile
1250
- };
1251
- }
1252
- function findNodeByName(sourceFile, name, predicate, getName) {
1253
- let result = null;
1254
- function visit(node) {
1255
- if (result) return;
1256
- if (predicate(node) && getName(node) === name) {
1257
- result = node;
1258
- return;
1259
- }
1260
- ts.forEachChild(node, visit);
1261
- }
1262
- visit(sourceFile);
1263
- return result;
1264
- }
1265
- function findClassByName(sourceFile, className) {
1266
- return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
1267
- }
1268
- function findInterfaceByName(sourceFile, interfaceName) {
1269
- return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
1270
- }
1271
- function findTypeAliasByName(sourceFile, aliasName) {
1272
- return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
1273
- }
1274
1213
 
1275
1214
  // src/analyzer/class-analyzer.ts
1276
- import * as ts4 from "typescript";
1215
+ import * as ts3 from "typescript";
1277
1216
 
1278
1217
  // src/analyzer/jsdoc-constraints.ts
1279
- import * as ts3 from "typescript";
1218
+ import * as ts2 from "typescript";
1280
1219
 
1281
1220
  // src/analyzer/tsdoc-parser.ts
1282
- import * as ts2 from "typescript";
1221
+ import * as ts from "typescript";
1283
1222
  import {
1284
1223
  TSDocParser,
1285
1224
  TSDocConfiguration,
@@ -1377,10 +1316,10 @@ function parseTSDocTags(node, file = "", options) {
1377
1316
  let placeholderProvenance;
1378
1317
  const sourceFile = node.getSourceFile();
1379
1318
  const sourceText = sourceFile.getFullText();
1380
- const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
1319
+ const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
1381
1320
  if (commentRanges) {
1382
1321
  for (const range of commentRanges) {
1383
- if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
1322
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
1384
1323
  continue;
1385
1324
  }
1386
1325
  const commentText = sourceText.substring(range.pos, range.end);
@@ -1398,26 +1337,31 @@ function parseTSDocTags(node, file = "", options) {
1398
1337
  const text2 = extractBlockText(block).trim();
1399
1338
  if (text2 === "") continue;
1400
1339
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1401
- if (tagName === "displayName") {
1402
- if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1403
- displayName = text2;
1404
- displayNameProvenance = provenance2;
1405
- }
1406
- } else if (tagName === "format") {
1407
- annotations.push({
1408
- kind: "annotation",
1409
- annotationKind: "format",
1410
- value: text2,
1411
- provenance: provenance2
1412
- });
1413
- } else {
1414
- if (tagName === "description" && description === void 0) {
1340
+ switch (tagName) {
1341
+ case "displayName":
1342
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1343
+ displayName = text2;
1344
+ displayNameProvenance = provenance2;
1345
+ }
1346
+ break;
1347
+ case "format":
1348
+ annotations.push({
1349
+ kind: "annotation",
1350
+ annotationKind: "format",
1351
+ value: text2,
1352
+ provenance: provenance2
1353
+ });
1354
+ break;
1355
+ case "description":
1415
1356
  description = text2;
1416
1357
  descriptionProvenance = provenance2;
1417
- } else if (tagName === "placeholder" && placeholder === void 0) {
1418
- placeholder = text2;
1419
- placeholderProvenance = provenance2;
1420
- }
1358
+ break;
1359
+ case "placeholder":
1360
+ if (placeholder === void 0) {
1361
+ placeholder = text2;
1362
+ placeholderProvenance = provenance2;
1363
+ }
1364
+ break;
1421
1365
  }
1422
1366
  continue;
1423
1367
  }
@@ -1447,6 +1391,13 @@ function parseTSDocTags(node, file = "", options) {
1447
1391
  descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1448
1392
  }
1449
1393
  }
1394
+ if (description === void 0) {
1395
+ const summary = extractPlainText(docComment.summarySection).trim();
1396
+ if (summary !== "") {
1397
+ description = summary;
1398
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
1399
+ }
1400
+ }
1450
1401
  }
1451
1402
  }
1452
1403
  if (displayName !== void 0 && displayNameProvenance !== void 0) {
@@ -1473,7 +1424,7 @@ function parseTSDocTags(node, file = "", options) {
1473
1424
  provenance: placeholderProvenance
1474
1425
  });
1475
1426
  }
1476
- const jsDocTagsAll = ts2.getJSDocTags(node);
1427
+ const jsDocTagsAll = ts.getJSDocTags(node);
1477
1428
  for (const tag of jsDocTagsAll) {
1478
1429
  const tagName = normalizeConstraintTagName(tag.tagName.text);
1479
1430
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
@@ -1496,7 +1447,7 @@ function parseTSDocTags(node, file = "", options) {
1496
1447
  function extractDisplayNameMetadata(node) {
1497
1448
  let displayName;
1498
1449
  const memberDisplayNames = /* @__PURE__ */ new Map();
1499
- for (const tag of ts2.getJSDocTags(node)) {
1450
+ for (const tag of ts.getJSDocTags(node)) {
1500
1451
  const tagName = normalizeConstraintTagName(tag.tagName.text);
1501
1452
  if (tagName !== "displayName") continue;
1502
1453
  const commentText = getTagCommentText(tag);
@@ -1517,11 +1468,11 @@ function extractDisplayNameMetadata(node) {
1517
1468
  }
1518
1469
  function extractPathTarget(text) {
1519
1470
  const trimmed = text.trimStart();
1520
- const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
1521
- if (!match?.[1] || !match[2]) return null;
1471
+ const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
1472
+ if (!match?.[1]) return null;
1522
1473
  return {
1523
1474
  path: { segments: [match[1]] },
1524
- remainingText: match[2]
1475
+ remainingText: match[2] ?? ""
1525
1476
  };
1526
1477
  }
1527
1478
  function extractBlockText(block) {
@@ -1784,7 +1735,7 @@ function getTagCommentText(tag) {
1784
1735
  if (typeof tag.comment === "string") {
1785
1736
  return tag.comment;
1786
1737
  }
1787
- return ts2.getTextOfJSDocComment(tag.comment);
1738
+ return ts.getTextOfJSDocComment(tag.comment);
1788
1739
  }
1789
1740
 
1790
1741
  // src/analyzer/jsdoc-constraints.ts
@@ -1799,18 +1750,18 @@ function extractJSDocAnnotationNodes(node, file = "", options) {
1799
1750
  function extractDefaultValueAnnotation(initializer, file = "") {
1800
1751
  if (!initializer) return null;
1801
1752
  let value;
1802
- if (ts3.isStringLiteral(initializer)) {
1753
+ if (ts2.isStringLiteral(initializer)) {
1803
1754
  value = initializer.text;
1804
- } else if (ts3.isNumericLiteral(initializer)) {
1755
+ } else if (ts2.isNumericLiteral(initializer)) {
1805
1756
  value = Number(initializer.text);
1806
- } else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
1757
+ } else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
1807
1758
  value = true;
1808
- } else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
1759
+ } else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
1809
1760
  value = false;
1810
- } else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
1761
+ } else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
1811
1762
  value = null;
1812
- } else if (ts3.isPrefixUnaryExpression(initializer)) {
1813
- if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
1763
+ } else if (ts2.isPrefixUnaryExpression(initializer)) {
1764
+ if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
1814
1765
  value = -Number(initializer.operand.text);
1815
1766
  }
1816
1767
  }
@@ -1832,10 +1783,10 @@ function extractDefaultValueAnnotation(initializer, file = "") {
1832
1783
 
1833
1784
  // src/analyzer/class-analyzer.ts
1834
1785
  function isObjectType(type) {
1835
- return !!(type.flags & ts4.TypeFlags.Object);
1786
+ return !!(type.flags & ts3.TypeFlags.Object);
1836
1787
  }
1837
1788
  function isTypeReference(type) {
1838
- return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1789
+ return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
1839
1790
  }
1840
1791
  var RESOLVING_TYPE_PLACEHOLDER = {
1841
1792
  kind: "object",
@@ -1865,7 +1816,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1865
1816
  const instanceMethods = [];
1866
1817
  const staticMethods = [];
1867
1818
  for (const member of classDecl.members) {
1868
- if (ts4.isPropertyDeclaration(member)) {
1819
+ if (ts3.isPropertyDeclaration(member)) {
1869
1820
  const fieldNode = analyzeFieldToIR(
1870
1821
  member,
1871
1822
  checker,
@@ -1878,10 +1829,10 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1878
1829
  fields.push(fieldNode);
1879
1830
  fieldLayouts.push({});
1880
1831
  }
1881
- } else if (ts4.isMethodDeclaration(member)) {
1832
+ } else if (ts3.isMethodDeclaration(member)) {
1882
1833
  const methodInfo = analyzeMethod(member, checker);
1883
1834
  if (methodInfo) {
1884
- const isStatic = member.modifiers?.some((m) => m.kind === ts4.SyntaxKind.StaticKeyword);
1835
+ const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
1885
1836
  if (isStatic) {
1886
1837
  staticMethods.push(methodInfo);
1887
1838
  } else {
@@ -1911,7 +1862,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1911
1862
  );
1912
1863
  const visiting = /* @__PURE__ */ new Set();
1913
1864
  for (const member of interfaceDecl.members) {
1914
- if (ts4.isPropertySignature(member)) {
1865
+ if (ts3.isPropertySignature(member)) {
1915
1866
  const fieldNode = analyzeInterfacePropertyToIR(
1916
1867
  member,
1917
1868
  checker,
@@ -1937,10 +1888,10 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1937
1888
  };
1938
1889
  }
1939
1890
  function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1940
- if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1891
+ if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1941
1892
  const sourceFile = typeAlias.getSourceFile();
1942
1893
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
1943
- const kindDesc = ts4.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1894
+ const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1944
1895
  return {
1945
1896
  ok: false,
1946
1897
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
@@ -1956,7 +1907,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1956
1907
  );
1957
1908
  const visiting = /* @__PURE__ */ new Set();
1958
1909
  for (const member of typeAlias.type.members) {
1959
- if (ts4.isPropertySignature(member)) {
1910
+ if (ts3.isPropertySignature(member)) {
1960
1911
  const fieldNode = analyzeInterfacePropertyToIR(
1961
1912
  member,
1962
1913
  checker,
@@ -1984,7 +1935,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1984
1935
  };
1985
1936
  }
1986
1937
  function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1987
- if (!ts4.isIdentifier(prop.name)) {
1938
+ if (!ts3.isIdentifier(prop.name)) {
1988
1939
  return null;
1989
1940
  }
1990
1941
  const name = prop.name.text;
@@ -2001,12 +1952,14 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
2001
1952
  extensionRegistry
2002
1953
  );
2003
1954
  const constraints = [];
2004
- if (prop.type) {
1955
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
2005
1956
  constraints.push(
2006
1957
  ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2007
1958
  );
2008
1959
  }
2009
- constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
1960
+ constraints.push(
1961
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
1962
+ );
2010
1963
  let annotations = [];
2011
1964
  annotations.push(
2012
1965
  ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
@@ -2027,7 +1980,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
2027
1980
  };
2028
1981
  }
2029
1982
  function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
2030
- if (!ts4.isIdentifier(prop.name)) {
1983
+ if (!ts3.isIdentifier(prop.name)) {
2031
1984
  return null;
2032
1985
  }
2033
1986
  const name = prop.name.text;
@@ -2044,12 +1997,14 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2044
1997
  extensionRegistry
2045
1998
  );
2046
1999
  const constraints = [];
2047
- if (prop.type) {
2000
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
2048
2001
  constraints.push(
2049
2002
  ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
2050
2003
  );
2051
2004
  }
2052
- constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
2005
+ constraints.push(
2006
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
2007
+ );
2053
2008
  let annotations = [];
2054
2009
  annotations.push(
2055
2010
  ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
@@ -2138,7 +2093,7 @@ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
2138
2093
  return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
2139
2094
  }
2140
2095
  function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
2141
- if (ts4.isParenthesizedTypeNode(typeNode)) {
2096
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2142
2097
  return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
2143
2098
  }
2144
2099
  const typeName = getTypeNodeRegistrationName(typeNode);
@@ -2153,8 +2108,8 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
2153
2108
  payload: null
2154
2109
  };
2155
2110
  }
2156
- if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
2157
- const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
2111
+ if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
2112
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2158
2113
  if (aliasDecl !== void 0) {
2159
2114
  return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
2160
2115
  }
@@ -2162,22 +2117,22 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
2162
2117
  return null;
2163
2118
  }
2164
2119
  function extractTypeNodeFromSource(sourceNode) {
2165
- if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
2120
+ if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
2166
2121
  return sourceNode.type;
2167
2122
  }
2168
- if (ts4.isTypeNode(sourceNode)) {
2123
+ if (ts3.isTypeNode(sourceNode)) {
2169
2124
  return sourceNode;
2170
2125
  }
2171
2126
  return void 0;
2172
2127
  }
2173
2128
  function getTypeNodeRegistrationName(typeNode) {
2174
- if (ts4.isTypeReferenceNode(typeNode)) {
2175
- return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
2129
+ if (ts3.isTypeReferenceNode(typeNode)) {
2130
+ return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
2176
2131
  }
2177
- if (ts4.isParenthesizedTypeNode(typeNode)) {
2132
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2178
2133
  return getTypeNodeRegistrationName(typeNode.type);
2179
2134
  }
2180
- if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
2135
+ if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
2181
2136
  return typeNode.getText();
2182
2137
  }
2183
2138
  return null;
@@ -2187,19 +2142,34 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2187
2142
  if (customType) {
2188
2143
  return customType;
2189
2144
  }
2190
- if (type.flags & ts4.TypeFlags.String) {
2145
+ const primitiveAlias = tryResolveNamedPrimitiveAlias(
2146
+ type,
2147
+ checker,
2148
+ file,
2149
+ typeRegistry,
2150
+ visiting,
2151
+ sourceNode,
2152
+ extensionRegistry
2153
+ );
2154
+ if (primitiveAlias) {
2155
+ return primitiveAlias;
2156
+ }
2157
+ if (type.flags & ts3.TypeFlags.String) {
2191
2158
  return { kind: "primitive", primitiveKind: "string" };
2192
2159
  }
2193
- if (type.flags & ts4.TypeFlags.Number) {
2160
+ if (type.flags & ts3.TypeFlags.Number) {
2194
2161
  return { kind: "primitive", primitiveKind: "number" };
2195
2162
  }
2196
- if (type.flags & ts4.TypeFlags.Boolean) {
2163
+ if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
2164
+ return { kind: "primitive", primitiveKind: "bigint" };
2165
+ }
2166
+ if (type.flags & ts3.TypeFlags.Boolean) {
2197
2167
  return { kind: "primitive", primitiveKind: "boolean" };
2198
2168
  }
2199
- if (type.flags & ts4.TypeFlags.Null) {
2169
+ if (type.flags & ts3.TypeFlags.Null) {
2200
2170
  return { kind: "primitive", primitiveKind: "null" };
2201
2171
  }
2202
- if (type.flags & ts4.TypeFlags.Undefined) {
2172
+ if (type.flags & ts3.TypeFlags.Undefined) {
2203
2173
  return { kind: "primitive", primitiveKind: "null" };
2204
2174
  }
2205
2175
  if (type.isStringLiteral()) {
@@ -2241,6 +2211,75 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2241
2211
  }
2242
2212
  return { kind: "primitive", primitiveKind: "string" };
2243
2213
  }
2214
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2215
+ if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2216
+ return null;
2217
+ }
2218
+ const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
2219
+ if (!aliasDecl) {
2220
+ return null;
2221
+ }
2222
+ const aliasName = aliasDecl.name.text;
2223
+ if (!typeRegistry[aliasName]) {
2224
+ const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
2225
+ const constraints = [
2226
+ ...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
2227
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
2228
+ ];
2229
+ const annotations = extractJSDocAnnotationNodes(
2230
+ aliasDecl,
2231
+ file,
2232
+ makeParseOptions(extensionRegistry)
2233
+ );
2234
+ typeRegistry[aliasName] = {
2235
+ name: aliasName,
2236
+ type: resolveAliasedPrimitiveTarget(
2237
+ aliasType,
2238
+ checker,
2239
+ file,
2240
+ typeRegistry,
2241
+ visiting,
2242
+ extensionRegistry
2243
+ ),
2244
+ ...constraints.length > 0 && { constraints },
2245
+ ...annotations.length > 0 && { annotations },
2246
+ provenance: provenanceForDeclaration(aliasDecl, file)
2247
+ };
2248
+ }
2249
+ return { kind: "reference", name: aliasName, typeArguments: [] };
2250
+ }
2251
+ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
2252
+ const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
2253
+ if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
2254
+ return void 0;
2255
+ }
2256
+ return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2257
+ }
2258
+ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2259
+ if (!ts3.isTypeReferenceNode(typeNode)) {
2260
+ return false;
2261
+ }
2262
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
2263
+ if (!aliasDecl) {
2264
+ return false;
2265
+ }
2266
+ const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2267
+ return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2268
+ }
2269
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2270
+ const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2271
+ if (nestedAliasDecl !== void 0) {
2272
+ return resolveAliasedPrimitiveTarget(
2273
+ checker.getTypeFromTypeNode(nestedAliasDecl.type),
2274
+ checker,
2275
+ file,
2276
+ typeRegistry,
2277
+ visiting,
2278
+ extensionRegistry
2279
+ );
2280
+ }
2281
+ return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
2282
+ }
2244
2283
  function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
2245
2284
  const typeName = getNamedTypeName(type);
2246
2285
  const namedDecl = getNamedTypeDeclaration(type);
@@ -2253,13 +2292,13 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2253
2292
  (memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
2254
2293
  );
2255
2294
  const nonNullTypes = allTypes.filter(
2256
- (memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
2295
+ (memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
2257
2296
  );
2258
2297
  const nonNullMembers = nonNullTypes.map((memberType, index) => ({
2259
2298
  memberType,
2260
2299
  sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
2261
2300
  }));
2262
- const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2301
+ const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
2263
2302
  const memberDisplayNames = /* @__PURE__ */ new Map();
2264
2303
  if (namedDecl) {
2265
2304
  for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
@@ -2288,7 +2327,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2288
2327
  const displayName = memberDisplayNames.get(String(value));
2289
2328
  return displayName !== void 0 ? { value, displayName } : { value };
2290
2329
  });
2291
- const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
2330
+ const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
2292
2331
  if (isBooleanUnion2) {
2293
2332
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
2294
2333
  const result = hasNull ? {
@@ -2374,7 +2413,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
2374
2413
  if (type.getProperties().length > 0) {
2375
2414
  return null;
2376
2415
  }
2377
- const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
2416
+ const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
2378
2417
  if (!indexInfo) {
2379
2418
  return null;
2380
2419
  }
@@ -2485,7 +2524,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2485
2524
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2486
2525
  if (!declaration) continue;
2487
2526
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
2488
- const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
2527
+ const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
2489
2528
  const propTypeNode = resolveTypeNode(
2490
2529
  propType,
2491
2530
  checker,
@@ -2530,11 +2569,11 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2530
2569
  for (const symbol of symbols) {
2531
2570
  const declarations = symbol.declarations;
2532
2571
  if (!declarations) continue;
2533
- const classDecl = declarations.find(ts4.isClassDeclaration);
2572
+ const classDecl = declarations.find(ts3.isClassDeclaration);
2534
2573
  if (classDecl) {
2535
2574
  const map = /* @__PURE__ */ new Map();
2536
2575
  for (const member of classDecl.members) {
2537
- if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
2576
+ if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
2538
2577
  const fieldNode = analyzeFieldToIR(
2539
2578
  member,
2540
2579
  checker,
@@ -2554,7 +2593,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2554
2593
  }
2555
2594
  return map;
2556
2595
  }
2557
- const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
2596
+ const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
2558
2597
  if (interfaceDecl) {
2559
2598
  return buildFieldNodeInfoMap(
2560
2599
  interfaceDecl.members,
@@ -2565,8 +2604,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2565
2604
  extensionRegistry
2566
2605
  );
2567
2606
  }
2568
- const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
2569
- if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
2607
+ const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
2608
+ if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
2570
2609
  return buildFieldNodeInfoMap(
2571
2610
  typeAliasDecl.type.members,
2572
2611
  checker,
@@ -2585,10 +2624,10 @@ function extractArrayElementTypeNode(sourceNode, checker) {
2585
2624
  return void 0;
2586
2625
  }
2587
2626
  const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2588
- if (ts4.isArrayTypeNode(resolvedTypeNode)) {
2627
+ if (ts3.isArrayTypeNode(resolvedTypeNode)) {
2589
2628
  return resolvedTypeNode.elementType;
2590
2629
  }
2591
- if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
2630
+ if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
2592
2631
  return resolvedTypeNode.typeArguments[0];
2593
2632
  }
2594
2633
  return void 0;
@@ -2599,17 +2638,17 @@ function extractUnionMemberTypeNodes(sourceNode, checker) {
2599
2638
  return [];
2600
2639
  }
2601
2640
  const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2602
- return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
2641
+ return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
2603
2642
  }
2604
2643
  function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
2605
- if (ts4.isParenthesizedTypeNode(typeNode)) {
2644
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2606
2645
  return resolveAliasedTypeNode(typeNode.type, checker, visited);
2607
2646
  }
2608
- if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
2647
+ if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
2609
2648
  return typeNode;
2610
2649
  }
2611
2650
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2612
- const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
2651
+ const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2613
2652
  if (aliasDecl === void 0 || visited.has(aliasDecl)) {
2614
2653
  return typeNode;
2615
2654
  }
@@ -2617,15 +2656,15 @@ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new
2617
2656
  return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
2618
2657
  }
2619
2658
  function isNullishTypeNode(typeNode) {
2620
- if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
2659
+ if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
2621
2660
  return true;
2622
2661
  }
2623
- return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
2662
+ return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
2624
2663
  }
2625
2664
  function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
2626
2665
  const map = /* @__PURE__ */ new Map();
2627
2666
  for (const member of members) {
2628
- if (ts4.isPropertySignature(member)) {
2667
+ if (ts3.isPropertySignature(member)) {
2629
2668
  const fieldNode = analyzeInterfacePropertyToIR(
2630
2669
  member,
2631
2670
  checker,
@@ -2647,7 +2686,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
2647
2686
  }
2648
2687
  var MAX_ALIAS_CHAIN_DEPTH = 8;
2649
2688
  function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
2650
- if (!ts4.isTypeReferenceNode(typeNode)) return [];
2689
+ if (!ts3.isTypeReferenceNode(typeNode)) return [];
2651
2690
  if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
2652
2691
  const aliasName = typeNode.typeName.getText();
2653
2692
  throw new Error(
@@ -2656,9 +2695,9 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
2656
2695
  }
2657
2696
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
2658
2697
  if (!symbol?.declarations) return [];
2659
- const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
2698
+ const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
2660
2699
  if (!aliasDecl) return [];
2661
- if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
2700
+ if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
2662
2701
  const aliasFieldType = resolveTypeNode(
2663
2702
  checker.getTypeAtLocation(aliasDecl.type),
2664
2703
  checker,
@@ -2674,13 +2713,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
2674
2713
  makeParseOptions(extensionRegistry, aliasFieldType)
2675
2714
  );
2676
2715
  constraints.push(
2677
- ...extractTypeAliasConstraintNodes(
2678
- aliasDecl.type,
2679
- checker,
2680
- file,
2681
- extensionRegistry,
2682
- depth + 1
2683
- )
2716
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
2684
2717
  );
2685
2718
  return constraints;
2686
2719
  }
@@ -2707,14 +2740,14 @@ function getNamedTypeName(type) {
2707
2740
  const symbol = type.getSymbol();
2708
2741
  if (symbol?.declarations) {
2709
2742
  const decl = symbol.declarations[0];
2710
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2711
- const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
2743
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
2744
+ const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
2712
2745
  if (name) return name;
2713
2746
  }
2714
2747
  }
2715
2748
  const aliasSymbol = type.aliasSymbol;
2716
2749
  if (aliasSymbol?.declarations) {
2717
- const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2750
+ const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
2718
2751
  if (aliasDecl) {
2719
2752
  return aliasDecl.name.text;
2720
2753
  }
@@ -2725,24 +2758,24 @@ function getNamedTypeDeclaration(type) {
2725
2758
  const symbol = type.getSymbol();
2726
2759
  if (symbol?.declarations) {
2727
2760
  const decl = symbol.declarations[0];
2728
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2761
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
2729
2762
  return decl;
2730
2763
  }
2731
2764
  }
2732
2765
  const aliasSymbol = type.aliasSymbol;
2733
2766
  if (aliasSymbol?.declarations) {
2734
- return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2767
+ return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
2735
2768
  }
2736
2769
  return void 0;
2737
2770
  }
2738
2771
  function analyzeMethod(method, checker) {
2739
- if (!ts4.isIdentifier(method.name)) {
2772
+ if (!ts3.isIdentifier(method.name)) {
2740
2773
  return null;
2741
2774
  }
2742
2775
  const name = method.name.text;
2743
2776
  const parameters = [];
2744
2777
  for (const param of method.parameters) {
2745
- if (ts4.isIdentifier(param.name)) {
2778
+ if (ts3.isIdentifier(param.name)) {
2746
2779
  const paramInfo = analyzeParameter(param, checker);
2747
2780
  parameters.push(paramInfo);
2748
2781
  }
@@ -2753,7 +2786,7 @@ function analyzeMethod(method, checker) {
2753
2786
  return { name, parameters, returnTypeNode, returnType };
2754
2787
  }
2755
2788
  function analyzeParameter(param, checker) {
2756
- const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
2789
+ const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
2757
2790
  const typeNode = param.type;
2758
2791
  const type = checker.getTypeAtLocation(param);
2759
2792
  const formSpecExportName = detectFormSpecReference(typeNode);
@@ -2762,28 +2795,932 @@ function analyzeParameter(param, checker) {
2762
2795
  }
2763
2796
  function detectFormSpecReference(typeNode) {
2764
2797
  if (!typeNode) return null;
2765
- if (!ts4.isTypeReferenceNode(typeNode)) return null;
2766
- const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
2798
+ if (!ts3.isTypeReferenceNode(typeNode)) return null;
2799
+ const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
2767
2800
  if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
2768
2801
  const typeArg = typeNode.typeArguments?.[0];
2769
- if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
2770
- if (ts4.isIdentifier(typeArg.exprName)) {
2802
+ if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
2803
+ if (ts3.isIdentifier(typeArg.exprName)) {
2771
2804
  return typeArg.exprName.text;
2772
2805
  }
2773
- if (ts4.isQualifiedName(typeArg.exprName)) {
2806
+ if (ts3.isQualifiedName(typeArg.exprName)) {
2774
2807
  return typeArg.exprName.right.text;
2775
2808
  }
2776
2809
  return null;
2777
2810
  }
2778
2811
 
2812
+ // src/analyzer/program.ts
2813
+ function createProgramContext(filePath) {
2814
+ const absolutePath = path.resolve(filePath);
2815
+ const fileDir = path.dirname(absolutePath);
2816
+ const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
2817
+ let compilerOptions;
2818
+ let fileNames;
2819
+ if (configPath) {
2820
+ const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
2821
+ if (configFile.error) {
2822
+ throw new Error(
2823
+ `Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
2824
+ );
2825
+ }
2826
+ const parsed = ts4.parseJsonConfigFileContent(
2827
+ configFile.config,
2828
+ ts4.sys,
2829
+ path.dirname(configPath)
2830
+ );
2831
+ if (parsed.errors.length > 0) {
2832
+ const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
2833
+ throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
2834
+ }
2835
+ compilerOptions = parsed.options;
2836
+ fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
2837
+ } else {
2838
+ compilerOptions = {
2839
+ target: ts4.ScriptTarget.ES2022,
2840
+ module: ts4.ModuleKind.NodeNext,
2841
+ moduleResolution: ts4.ModuleResolutionKind.NodeNext,
2842
+ strict: true,
2843
+ skipLibCheck: true,
2844
+ declaration: true
2845
+ };
2846
+ fileNames = [absolutePath];
2847
+ }
2848
+ const program = ts4.createProgram(fileNames, compilerOptions);
2849
+ const sourceFile = program.getSourceFile(absolutePath);
2850
+ if (!sourceFile) {
2851
+ throw new Error(`Could not find source file: ${absolutePath}`);
2852
+ }
2853
+ return {
2854
+ program,
2855
+ checker: program.getTypeChecker(),
2856
+ sourceFile
2857
+ };
2858
+ }
2859
+ function findNodeByName(sourceFile, name, predicate, getName) {
2860
+ let result = null;
2861
+ function visit(node) {
2862
+ if (result) return;
2863
+ if (predicate(node) && getName(node) === name) {
2864
+ result = node;
2865
+ return;
2866
+ }
2867
+ ts4.forEachChild(node, visit);
2868
+ }
2869
+ visit(sourceFile);
2870
+ return result;
2871
+ }
2872
+ function findClassByName(sourceFile, className) {
2873
+ return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
2874
+ }
2875
+ function findInterfaceByName(sourceFile, interfaceName) {
2876
+ return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
2877
+ }
2878
+ function findTypeAliasByName(sourceFile, aliasName) {
2879
+ return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
2880
+ }
2881
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
2882
+ const ctx = createProgramContext(filePath);
2883
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2884
+ if (classDecl !== null) {
2885
+ return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
2886
+ }
2887
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2888
+ if (interfaceDecl !== null) {
2889
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
2890
+ }
2891
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2892
+ if (typeAlias !== null) {
2893
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
2894
+ if (result.ok) {
2895
+ return result.analysis;
2896
+ }
2897
+ throw new Error(result.error);
2898
+ }
2899
+ throw new Error(
2900
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2901
+ );
2902
+ }
2903
+
2904
+ // src/validate/constraint-validator.ts
2905
+ import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
2906
+ function addContradiction(ctx, message, primary, related) {
2907
+ ctx.diagnostics.push({
2908
+ code: "CONTRADICTING_CONSTRAINTS",
2909
+ message,
2910
+ severity: "error",
2911
+ primaryLocation: primary,
2912
+ relatedLocations: [related]
2913
+ });
2914
+ }
2915
+ function addTypeMismatch(ctx, message, primary) {
2916
+ ctx.diagnostics.push({
2917
+ code: "TYPE_MISMATCH",
2918
+ message,
2919
+ severity: "error",
2920
+ primaryLocation: primary,
2921
+ relatedLocations: []
2922
+ });
2923
+ }
2924
+ function addUnknownExtension(ctx, message, primary) {
2925
+ ctx.diagnostics.push({
2926
+ code: "UNKNOWN_EXTENSION",
2927
+ message,
2928
+ severity: "warning",
2929
+ primaryLocation: primary,
2930
+ relatedLocations: []
2931
+ });
2932
+ }
2933
+ function addUnknownPathTarget(ctx, message, primary) {
2934
+ ctx.diagnostics.push({
2935
+ code: "UNKNOWN_PATH_TARGET",
2936
+ message,
2937
+ severity: "error",
2938
+ primaryLocation: primary,
2939
+ relatedLocations: []
2940
+ });
2941
+ }
2942
+ function addConstraintBroadening(ctx, message, primary, related) {
2943
+ ctx.diagnostics.push({
2944
+ code: "CONSTRAINT_BROADENING",
2945
+ message,
2946
+ severity: "error",
2947
+ primaryLocation: primary,
2948
+ relatedLocations: [related]
2949
+ });
2950
+ }
2951
+ function getExtensionIdFromConstraintId(constraintId) {
2952
+ const separator = constraintId.lastIndexOf("/");
2953
+ if (separator <= 0) {
2954
+ return null;
2955
+ }
2956
+ return constraintId.slice(0, separator);
2957
+ }
2958
+ function findNumeric(constraints, constraintKind) {
2959
+ return constraints.find((c) => c.constraintKind === constraintKind);
2960
+ }
2961
+ function findLength(constraints, constraintKind) {
2962
+ return constraints.find((c) => c.constraintKind === constraintKind);
2963
+ }
2964
+ function findAllowedMembers(constraints) {
2965
+ return constraints.filter(
2966
+ (c) => c.constraintKind === "allowedMembers"
2967
+ );
2968
+ }
2969
+ function findConstConstraints(constraints) {
2970
+ return constraints.filter(
2971
+ (c) => c.constraintKind === "const"
2972
+ );
2973
+ }
2974
+ function jsonValueEquals(left, right) {
2975
+ if (left === right) {
2976
+ return true;
2977
+ }
2978
+ if (Array.isArray(left) || Array.isArray(right)) {
2979
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
2980
+ return false;
2981
+ }
2982
+ return left.every((item, index) => jsonValueEquals(item, right[index]));
2983
+ }
2984
+ if (isJsonObject(left) || isJsonObject(right)) {
2985
+ if (!isJsonObject(left) || !isJsonObject(right)) {
2986
+ return false;
2987
+ }
2988
+ const leftKeys = Object.keys(left).sort();
2989
+ const rightKeys = Object.keys(right).sort();
2990
+ if (leftKeys.length !== rightKeys.length) {
2991
+ return false;
2992
+ }
2993
+ return leftKeys.every((key, index) => {
2994
+ const rightKey = rightKeys[index];
2995
+ if (rightKey !== key) {
2996
+ return false;
2997
+ }
2998
+ const leftValue = left[key];
2999
+ const rightValue = right[rightKey];
3000
+ return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
3001
+ });
3002
+ }
3003
+ return false;
3004
+ }
3005
+ function isJsonObject(value) {
3006
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3007
+ }
3008
+ function isOrderedBoundConstraint(constraint) {
3009
+ return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
3010
+ }
3011
+ function pathKey(constraint) {
3012
+ return constraint.path?.segments.join(".") ?? "";
3013
+ }
3014
+ function orderedBoundFamily(kind) {
3015
+ switch (kind) {
3016
+ case "minimum":
3017
+ case "exclusiveMinimum":
3018
+ return "numeric-lower";
3019
+ case "maximum":
3020
+ case "exclusiveMaximum":
3021
+ return "numeric-upper";
3022
+ case "minLength":
3023
+ return "minLength";
3024
+ case "minItems":
3025
+ return "minItems";
3026
+ case "maxLength":
3027
+ return "maxLength";
3028
+ case "maxItems":
3029
+ return "maxItems";
3030
+ default: {
3031
+ const _exhaustive = kind;
3032
+ return _exhaustive;
3033
+ }
3034
+ }
3035
+ }
3036
+ function isNumericLowerKind(kind) {
3037
+ return kind === "minimum" || kind === "exclusiveMinimum";
3038
+ }
3039
+ function isNumericUpperKind(kind) {
3040
+ return kind === "maximum" || kind === "exclusiveMaximum";
3041
+ }
3042
+ function describeConstraintTag(constraint) {
3043
+ return `@${constraint.constraintKind}`;
3044
+ }
3045
+ function compareConstraintStrength(current, previous) {
3046
+ const family = orderedBoundFamily(current.constraintKind);
3047
+ if (family === "numeric-lower") {
3048
+ if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
3049
+ throw new Error("numeric-lower family received non-numeric lower-bound constraint");
3050
+ }
3051
+ if (current.value !== previous.value) {
3052
+ return current.value > previous.value ? 1 : -1;
3053
+ }
3054
+ if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
3055
+ return 1;
3056
+ }
3057
+ if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
3058
+ return -1;
3059
+ }
3060
+ return 0;
3061
+ }
3062
+ if (family === "numeric-upper") {
3063
+ if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
3064
+ throw new Error("numeric-upper family received non-numeric upper-bound constraint");
3065
+ }
3066
+ if (current.value !== previous.value) {
3067
+ return current.value < previous.value ? 1 : -1;
3068
+ }
3069
+ if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
3070
+ return 1;
3071
+ }
3072
+ if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
3073
+ return -1;
3074
+ }
3075
+ return 0;
3076
+ }
3077
+ switch (family) {
3078
+ case "minLength":
3079
+ case "minItems":
3080
+ if (current.value === previous.value) {
3081
+ return 0;
3082
+ }
3083
+ return current.value > previous.value ? 1 : -1;
3084
+ case "maxLength":
3085
+ case "maxItems":
3086
+ if (current.value === previous.value) {
3087
+ return 0;
3088
+ }
3089
+ return current.value < previous.value ? 1 : -1;
3090
+ default: {
3091
+ const _exhaustive = family;
3092
+ return _exhaustive;
3093
+ }
3094
+ }
3095
+ }
3096
+ function checkConstraintBroadening(ctx, fieldName, constraints) {
3097
+ const strongestByKey = /* @__PURE__ */ new Map();
3098
+ for (const constraint of constraints) {
3099
+ if (!isOrderedBoundConstraint(constraint)) {
3100
+ continue;
3101
+ }
3102
+ const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
3103
+ const previous = strongestByKey.get(key);
3104
+ if (previous === void 0) {
3105
+ strongestByKey.set(key, constraint);
3106
+ continue;
3107
+ }
3108
+ const strength = compareConstraintStrength(constraint, previous);
3109
+ if (strength < 0) {
3110
+ const displayFieldName = formatPathTargetFieldName(
3111
+ fieldName,
3112
+ constraint.path?.segments ?? []
3113
+ );
3114
+ addConstraintBroadening(
3115
+ ctx,
3116
+ `Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
3117
+ constraint.provenance,
3118
+ previous.provenance
3119
+ );
3120
+ continue;
3121
+ }
3122
+ if (strength <= 0) {
3123
+ continue;
3124
+ }
3125
+ strongestByKey.set(key, constraint);
3126
+ }
3127
+ }
3128
+ function compareCustomConstraintStrength(current, previous) {
3129
+ const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
3130
+ const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
3131
+ switch (current.role.bound) {
3132
+ case "lower":
3133
+ return equalPayloadTiebreaker;
3134
+ case "upper":
3135
+ return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
3136
+ case "exact":
3137
+ return order === 0 ? 0 : Number.NaN;
3138
+ default: {
3139
+ const _exhaustive = current.role.bound;
3140
+ return _exhaustive;
3141
+ }
3142
+ }
3143
+ }
3144
+ function compareSemanticInclusivity(currentInclusive, previousInclusive) {
3145
+ if (currentInclusive === previousInclusive) {
3146
+ return 0;
3147
+ }
3148
+ return currentInclusive ? -1 : 1;
3149
+ }
3150
+ function customConstraintsContradict(lower, upper) {
3151
+ const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
3152
+ if (order > 0) {
3153
+ return true;
3154
+ }
3155
+ if (order < 0) {
3156
+ return false;
3157
+ }
3158
+ return !lower.role.inclusive || !upper.role.inclusive;
3159
+ }
3160
+ function describeCustomConstraintTag(constraint) {
3161
+ return constraint.provenance.tagName ?? constraint.constraintId;
3162
+ }
3163
+ function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
3164
+ if (ctx.extensionRegistry === void 0) {
3165
+ return;
3166
+ }
3167
+ const strongestByKey = /* @__PURE__ */ new Map();
3168
+ const lowerByFamily = /* @__PURE__ */ new Map();
3169
+ const upperByFamily = /* @__PURE__ */ new Map();
3170
+ for (const constraint of constraints) {
3171
+ if (constraint.constraintKind !== "custom") {
3172
+ continue;
3173
+ }
3174
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3175
+ if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
3176
+ continue;
3177
+ }
3178
+ const entry = {
3179
+ constraint,
3180
+ comparePayloads: registration.comparePayloads,
3181
+ role: registration.semanticRole
3182
+ };
3183
+ const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
3184
+ const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
3185
+ const previous = strongestByKey.get(boundKey);
3186
+ if (previous !== void 0) {
3187
+ const strength = compareCustomConstraintStrength(entry, previous);
3188
+ if (Number.isNaN(strength)) {
3189
+ addContradiction(
3190
+ ctx,
3191
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
3192
+ constraint.provenance,
3193
+ previous.constraint.provenance
3194
+ );
3195
+ continue;
3196
+ }
3197
+ if (strength < 0) {
3198
+ addConstraintBroadening(
3199
+ ctx,
3200
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
3201
+ constraint.provenance,
3202
+ previous.constraint.provenance
3203
+ );
3204
+ continue;
3205
+ }
3206
+ if (strength > 0) {
3207
+ strongestByKey.set(boundKey, entry);
3208
+ }
3209
+ } else {
3210
+ strongestByKey.set(boundKey, entry);
3211
+ }
3212
+ if (registration.semanticRole.bound === "lower") {
3213
+ lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3214
+ } else if (registration.semanticRole.bound === "upper") {
3215
+ upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3216
+ }
3217
+ }
3218
+ for (const [familyKey, lower] of lowerByFamily) {
3219
+ const upper = upperByFamily.get(familyKey);
3220
+ if (upper === void 0) {
3221
+ continue;
3222
+ }
3223
+ if (!customConstraintsContradict(lower, upper)) {
3224
+ continue;
3225
+ }
3226
+ addContradiction(
3227
+ ctx,
3228
+ `Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
3229
+ lower.constraint.provenance,
3230
+ upper.constraint.provenance
3231
+ );
3232
+ }
3233
+ }
3234
+ function checkNumericContradictions(ctx, fieldName, constraints) {
3235
+ const min = findNumeric(constraints, "minimum");
3236
+ const max = findNumeric(constraints, "maximum");
3237
+ const exMin = findNumeric(constraints, "exclusiveMinimum");
3238
+ const exMax = findNumeric(constraints, "exclusiveMaximum");
3239
+ if (min !== void 0 && max !== void 0 && min.value > max.value) {
3240
+ addContradiction(
3241
+ ctx,
3242
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
3243
+ min.provenance,
3244
+ max.provenance
3245
+ );
3246
+ }
3247
+ if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
3248
+ addContradiction(
3249
+ ctx,
3250
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
3251
+ exMin.provenance,
3252
+ max.provenance
3253
+ );
3254
+ }
3255
+ if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
3256
+ addContradiction(
3257
+ ctx,
3258
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3259
+ min.provenance,
3260
+ exMax.provenance
3261
+ );
3262
+ }
3263
+ if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
3264
+ addContradiction(
3265
+ ctx,
3266
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3267
+ exMin.provenance,
3268
+ exMax.provenance
3269
+ );
3270
+ }
3271
+ }
3272
+ function checkLengthContradictions(ctx, fieldName, constraints) {
3273
+ const minLen = findLength(constraints, "minLength");
3274
+ const maxLen = findLength(constraints, "maxLength");
3275
+ if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
3276
+ addContradiction(
3277
+ ctx,
3278
+ `Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
3279
+ minLen.provenance,
3280
+ maxLen.provenance
3281
+ );
3282
+ }
3283
+ const minItems = findLength(constraints, "minItems");
3284
+ const maxItems = findLength(constraints, "maxItems");
3285
+ if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
3286
+ addContradiction(
3287
+ ctx,
3288
+ `Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
3289
+ minItems.provenance,
3290
+ maxItems.provenance
3291
+ );
3292
+ }
3293
+ }
3294
+ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
3295
+ const members = findAllowedMembers(constraints);
3296
+ if (members.length < 2) return;
3297
+ const firstSet = new Set(members[0]?.members ?? []);
3298
+ for (let i = 1; i < members.length; i++) {
3299
+ const current = members[i];
3300
+ if (current === void 0) continue;
3301
+ for (const m of firstSet) {
3302
+ if (!current.members.includes(m)) {
3303
+ firstSet.delete(m);
3304
+ }
3305
+ }
3306
+ }
3307
+ if (firstSet.size === 0) {
3308
+ const first = members[0];
3309
+ const second = members[1];
3310
+ if (first !== void 0 && second !== void 0) {
3311
+ addContradiction(
3312
+ ctx,
3313
+ `Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
3314
+ first.provenance,
3315
+ second.provenance
3316
+ );
3317
+ }
3318
+ }
3319
+ }
3320
+ function checkConstContradictions(ctx, fieldName, constraints) {
3321
+ const constConstraints = findConstConstraints(constraints);
3322
+ if (constConstraints.length < 2) return;
3323
+ const first = constConstraints[0];
3324
+ if (first === void 0) return;
3325
+ for (let i = 1; i < constConstraints.length; i++) {
3326
+ const current = constConstraints[i];
3327
+ if (current === void 0) continue;
3328
+ if (jsonValueEquals(first.value, current.value)) {
3329
+ continue;
3330
+ }
3331
+ addContradiction(
3332
+ ctx,
3333
+ `Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
3334
+ first.provenance,
3335
+ current.provenance
3336
+ );
3337
+ }
3338
+ }
3339
+ function typeLabel(type) {
3340
+ switch (type.kind) {
3341
+ case "primitive":
3342
+ return type.primitiveKind;
3343
+ case "enum":
3344
+ return "enum";
3345
+ case "array":
3346
+ return "array";
3347
+ case "object":
3348
+ return "object";
3349
+ case "record":
3350
+ return "record";
3351
+ case "union":
3352
+ return "union";
3353
+ case "reference":
3354
+ return `reference(${type.name})`;
3355
+ case "dynamic":
3356
+ return `dynamic(${type.dynamicKind})`;
3357
+ case "custom":
3358
+ return `custom(${type.typeId})`;
3359
+ default: {
3360
+ const _exhaustive = type;
3361
+ return String(_exhaustive);
3362
+ }
3363
+ }
3364
+ }
3365
+ function dereferenceType(ctx, type) {
3366
+ let current = type;
3367
+ const seen = /* @__PURE__ */ new Set();
3368
+ while (current.kind === "reference") {
3369
+ if (seen.has(current.name)) {
3370
+ return current;
3371
+ }
3372
+ seen.add(current.name);
3373
+ const definition = ctx.typeRegistry[current.name];
3374
+ if (definition === void 0) {
3375
+ return current;
3376
+ }
3377
+ current = definition.type;
3378
+ }
3379
+ return current;
3380
+ }
3381
+ function collectReferencedTypeConstraints(ctx, type) {
3382
+ const collected = [];
3383
+ let current = type;
3384
+ const seen = /* @__PURE__ */ new Set();
3385
+ while (current.kind === "reference") {
3386
+ if (seen.has(current.name)) {
3387
+ break;
3388
+ }
3389
+ seen.add(current.name);
3390
+ const definition = ctx.typeRegistry[current.name];
3391
+ if (definition === void 0) {
3392
+ break;
3393
+ }
3394
+ if (definition.constraints !== void 0) {
3395
+ collected.push(...definition.constraints);
3396
+ }
3397
+ current = definition.type;
3398
+ }
3399
+ return collected;
3400
+ }
3401
+ function resolvePathTargetType(ctx, type, segments) {
3402
+ const effectiveType = dereferenceType(ctx, type);
3403
+ if (segments.length === 0) {
3404
+ return { kind: "resolved", type: effectiveType };
3405
+ }
3406
+ if (effectiveType.kind === "array") {
3407
+ return resolvePathTargetType(ctx, effectiveType.items, segments);
3408
+ }
3409
+ if (effectiveType.kind === "object") {
3410
+ const [segment, ...rest] = segments;
3411
+ if (segment === void 0) {
3412
+ throw new Error("Invariant violation: object path traversal requires a segment");
3413
+ }
3414
+ const property = effectiveType.properties.find((prop) => prop.name === segment);
3415
+ if (property === void 0) {
3416
+ return { kind: "missing-property", segment };
3417
+ }
3418
+ return resolvePathTargetType(ctx, property.type, rest);
3419
+ }
3420
+ return { kind: "unresolvable", type: effectiveType };
3421
+ }
3422
+ function isNullType(type) {
3423
+ return type.kind === "primitive" && type.primitiveKind === "null";
3424
+ }
3425
+ function collectCustomConstraintCandidateTypes(ctx, type) {
3426
+ const effectiveType = dereferenceType(ctx, type);
3427
+ const candidates = [effectiveType];
3428
+ if (effectiveType.kind === "array") {
3429
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
3430
+ }
3431
+ if (effectiveType.kind === "union") {
3432
+ const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
3433
+ const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
3434
+ if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
3435
+ const [nullableMember] = nonNullMembers;
3436
+ if (nullableMember !== void 0) {
3437
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
3438
+ }
3439
+ }
3440
+ }
3441
+ return candidates;
3442
+ }
3443
+ function formatPathTargetFieldName(fieldName, path3) {
3444
+ return path3.length === 0 ? fieldName : `${fieldName}.${path3.join(".")}`;
3445
+ }
3446
+ function checkConstraintOnType(ctx, fieldName, type, constraint) {
3447
+ const effectiveType = dereferenceType(ctx, type);
3448
+ const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
3449
+ const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
3450
+ const isArray = effectiveType.kind === "array";
3451
+ const isEnum = effectiveType.kind === "enum";
3452
+ const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
3453
+ const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
3454
+ const label = typeLabel(effectiveType);
3455
+ const ck = constraint.constraintKind;
3456
+ switch (ck) {
3457
+ case "minimum":
3458
+ case "maximum":
3459
+ case "exclusiveMinimum":
3460
+ case "exclusiveMaximum":
3461
+ case "multipleOf": {
3462
+ if (!isNumber) {
3463
+ addTypeMismatch(
3464
+ ctx,
3465
+ `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
3466
+ constraint.provenance
3467
+ );
3468
+ }
3469
+ break;
3470
+ }
3471
+ case "minLength":
3472
+ case "maxLength":
3473
+ case "pattern": {
3474
+ if (!isString && !isStringArray) {
3475
+ addTypeMismatch(
3476
+ ctx,
3477
+ `Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
3478
+ constraint.provenance
3479
+ );
3480
+ }
3481
+ break;
3482
+ }
3483
+ case "minItems":
3484
+ case "maxItems":
3485
+ case "uniqueItems": {
3486
+ if (!isArray) {
3487
+ addTypeMismatch(
3488
+ ctx,
3489
+ `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
3490
+ constraint.provenance
3491
+ );
3492
+ }
3493
+ break;
3494
+ }
3495
+ case "allowedMembers": {
3496
+ if (!isEnum) {
3497
+ addTypeMismatch(
3498
+ ctx,
3499
+ `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
3500
+ constraint.provenance
3501
+ );
3502
+ }
3503
+ break;
3504
+ }
3505
+ case "const": {
3506
+ const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
3507
+ effectiveType.primitiveKind
3508
+ ) || effectiveType.kind === "enum";
3509
+ if (!isPrimitiveConstType) {
3510
+ addTypeMismatch(
3511
+ ctx,
3512
+ `Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
3513
+ constraint.provenance
3514
+ );
3515
+ break;
3516
+ }
3517
+ if (effectiveType.kind === "primitive") {
3518
+ const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
3519
+ const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
3520
+ if (valueType !== expectedValueType) {
3521
+ addTypeMismatch(
3522
+ ctx,
3523
+ `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
3524
+ constraint.provenance
3525
+ );
3526
+ }
3527
+ break;
3528
+ }
3529
+ const memberValues = effectiveType.members.map((member) => member.value);
3530
+ if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
3531
+ addTypeMismatch(
3532
+ ctx,
3533
+ `Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
3534
+ constraint.provenance
3535
+ );
3536
+ }
3537
+ break;
3538
+ }
3539
+ case "custom": {
3540
+ checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
3541
+ break;
3542
+ }
3543
+ default: {
3544
+ const _exhaustive = constraint;
3545
+ throw new Error(
3546
+ `Unhandled constraint kind: ${_exhaustive.constraintKind}`
3547
+ );
3548
+ }
3549
+ }
3550
+ }
3551
+ function checkTypeApplicability(ctx, fieldName, type, constraints) {
3552
+ for (const constraint of constraints) {
3553
+ if (constraint.path) {
3554
+ const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
3555
+ const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
3556
+ if (resolution.kind === "missing-property") {
3557
+ addUnknownPathTarget(
3558
+ ctx,
3559
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
3560
+ constraint.provenance
3561
+ );
3562
+ continue;
3563
+ }
3564
+ if (resolution.kind === "unresolvable") {
3565
+ addTypeMismatch(
3566
+ ctx,
3567
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
3568
+ constraint.provenance
3569
+ );
3570
+ continue;
3571
+ }
3572
+ checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
3573
+ continue;
3574
+ }
3575
+ checkConstraintOnType(ctx, fieldName, type, constraint);
3576
+ }
3577
+ }
3578
+ function checkCustomConstraint(ctx, fieldName, type, constraint) {
3579
+ if (ctx.extensionRegistry === void 0) return;
3580
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3581
+ if (registration === void 0) {
3582
+ addUnknownExtension(
3583
+ ctx,
3584
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
3585
+ constraint.provenance
3586
+ );
3587
+ return;
3588
+ }
3589
+ const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
3590
+ const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
3591
+ if (normalizedTagName !== void 0) {
3592
+ const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
3593
+ const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
3594
+ if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
3595
+ (candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
3596
+ )) {
3597
+ addTypeMismatch(
3598
+ ctx,
3599
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3600
+ constraint.provenance
3601
+ );
3602
+ return;
3603
+ }
3604
+ }
3605
+ if (registration.applicableTypes === null) {
3606
+ if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
3607
+ addTypeMismatch(
3608
+ ctx,
3609
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3610
+ constraint.provenance
3611
+ );
3612
+ }
3613
+ return;
3614
+ }
3615
+ const applicableTypes = registration.applicableTypes;
3616
+ const matchesApplicableType = candidateTypes.some(
3617
+ (candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
3618
+ );
3619
+ if (!matchesApplicableType) {
3620
+ addTypeMismatch(
3621
+ ctx,
3622
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3623
+ constraint.provenance
3624
+ );
3625
+ }
3626
+ }
3627
+ function validateFieldNode(ctx, field) {
3628
+ validateConstraints(ctx, field.name, field.type, [
3629
+ ...collectReferencedTypeConstraints(ctx, field.type),
3630
+ ...field.constraints
3631
+ ]);
3632
+ if (field.type.kind === "object") {
3633
+ for (const prop of field.type.properties) {
3634
+ validateObjectProperty(ctx, field.name, prop);
3635
+ }
3636
+ }
3637
+ }
3638
+ function validateObjectProperty(ctx, parentName, prop) {
3639
+ const qualifiedName = `${parentName}.${prop.name}`;
3640
+ validateConstraints(ctx, qualifiedName, prop.type, [
3641
+ ...collectReferencedTypeConstraints(ctx, prop.type),
3642
+ ...prop.constraints
3643
+ ]);
3644
+ if (prop.type.kind === "object") {
3645
+ for (const nestedProp of prop.type.properties) {
3646
+ validateObjectProperty(ctx, qualifiedName, nestedProp);
3647
+ }
3648
+ }
3649
+ }
3650
+ function validateConstraints(ctx, name, type, constraints) {
3651
+ checkNumericContradictions(ctx, name, constraints);
3652
+ checkLengthContradictions(ctx, name, constraints);
3653
+ checkAllowedMembersContradiction(ctx, name, constraints);
3654
+ checkConstContradictions(ctx, name, constraints);
3655
+ checkConstraintBroadening(ctx, name, constraints);
3656
+ checkCustomConstraintSemantics(ctx, name, constraints);
3657
+ checkTypeApplicability(ctx, name, type, constraints);
3658
+ }
3659
+ function validateElement(ctx, element) {
3660
+ switch (element.kind) {
3661
+ case "field":
3662
+ validateFieldNode(ctx, element);
3663
+ break;
3664
+ case "group":
3665
+ for (const child of element.elements) {
3666
+ validateElement(ctx, child);
3667
+ }
3668
+ break;
3669
+ case "conditional":
3670
+ for (const child of element.elements) {
3671
+ validateElement(ctx, child);
3672
+ }
3673
+ break;
3674
+ default: {
3675
+ const _exhaustive = element;
3676
+ throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
3677
+ }
3678
+ }
3679
+ }
3680
+ function validateIR(ir, options) {
3681
+ const ctx = {
3682
+ diagnostics: [],
3683
+ extensionRegistry: options?.extensionRegistry,
3684
+ typeRegistry: ir.typeRegistry
3685
+ };
3686
+ for (const element of ir.elements) {
3687
+ validateElement(ctx, element);
3688
+ }
3689
+ return {
3690
+ diagnostics: ctx.diagnostics,
3691
+ valid: ctx.diagnostics.every((d) => d.severity !== "error")
3692
+ };
3693
+ }
3694
+
2779
3695
  // src/generators/class-schema.ts
2780
3696
  function generateClassSchemas(analysis, source, options) {
2781
3697
  const ir = canonicalizeTSDoc(analysis, source);
3698
+ const validationResult = validateIR(ir, {
3699
+ ...options?.extensionRegistry !== void 0 && {
3700
+ extensionRegistry: options.extensionRegistry
3701
+ },
3702
+ ...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
3703
+ });
3704
+ if (!validationResult.valid) {
3705
+ throw new Error(formatValidationError(validationResult.diagnostics));
3706
+ }
2782
3707
  return {
2783
3708
  jsonSchema: generateJsonSchemaFromIR(ir, options),
2784
3709
  uiSchema: generateUiSchemaFromIR(ir)
2785
3710
  };
2786
3711
  }
3712
+ function formatValidationError(diagnostics) {
3713
+ const lines = diagnostics.map((diagnostic) => {
3714
+ const primary = formatLocation(diagnostic.primaryLocation);
3715
+ const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
3716
+ return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
3717
+ });
3718
+ return `FormSpec validation failed:
3719
+ ${lines.map((line) => `- ${line}`).join("\n")}`;
3720
+ }
3721
+ function formatLocation(location) {
3722
+ return `${location.file}:${String(location.line)}:${String(location.column)}`;
3723
+ }
2787
3724
  function generateSchemasFromClass(options) {
2788
3725
  const ctx = createProgramContext(options.filePath);
2789
3726
  const classDecl = findClassByName(ctx.sourceFile, options.className);
@@ -2806,50 +3743,18 @@ function generateSchemasFromClass(options) {
2806
3743
  );
2807
3744
  }
2808
3745
  function generateSchemas(options) {
2809
- const ctx = createProgramContext(options.filePath);
2810
- const source = { file: options.filePath };
2811
- const classDecl = findClassByName(ctx.sourceFile, options.typeName);
2812
- if (classDecl) {
2813
- const analysis = analyzeClassToIR(
2814
- classDecl,
2815
- ctx.checker,
2816
- options.filePath,
2817
- options.extensionRegistry
2818
- );
2819
- return generateClassSchemas(analysis, source, options);
2820
- }
2821
- const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
2822
- if (interfaceDecl) {
2823
- const analysis = analyzeInterfaceToIR(
2824
- interfaceDecl,
2825
- ctx.checker,
2826
- options.filePath,
2827
- options.extensionRegistry
2828
- );
2829
- return generateClassSchemas(analysis, source, options);
2830
- }
2831
- const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
2832
- if (typeAlias) {
2833
- const result = analyzeTypeAliasToIR(
2834
- typeAlias,
2835
- ctx.checker,
2836
- options.filePath,
2837
- options.extensionRegistry
2838
- );
2839
- if (result.ok) {
2840
- return generateClassSchemas(result.analysis, source, options);
2841
- }
2842
- throw new Error(result.error);
2843
- }
2844
- throw new Error(
2845
- `Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
3746
+ const analysis = analyzeNamedTypeToIR(
3747
+ options.filePath,
3748
+ options.typeName,
3749
+ options.extensionRegistry
2846
3750
  );
3751
+ return generateClassSchemas(analysis, { file: options.filePath }, options);
2847
3752
  }
2848
3753
 
2849
3754
  // src/generators/mixed-authoring.ts
2850
3755
  function buildMixedAuthoringSchemas(options) {
2851
3756
  const { filePath, typeName, overlays, ...schemaOptions } = options;
2852
- const analysis = analyzeNamedType(filePath, typeName, schemaOptions.extensionRegistry);
3757
+ const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
2853
3758
  const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2854
3759
  const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2855
3760
  return {
@@ -2857,29 +3762,6 @@ function buildMixedAuthoringSchemas(options) {
2857
3762
  uiSchema: generateUiSchemaFromIR(ir)
2858
3763
  };
2859
3764
  }
2860
- function analyzeNamedType(filePath, typeName, extensionRegistry) {
2861
- const ctx = createProgramContext(filePath);
2862
- const source = { file: filePath };
2863
- const classDecl = findClassByName(ctx.sourceFile, typeName);
2864
- if (classDecl !== null) {
2865
- return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
2866
- }
2867
- const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2868
- if (interfaceDecl !== null) {
2869
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
2870
- }
2871
- const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2872
- if (typeAlias !== null) {
2873
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
2874
- if (result.ok) {
2875
- return result.analysis;
2876
- }
2877
- throw new Error(result.error);
2878
- }
2879
- throw new Error(
2880
- `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2881
- );
2882
- }
2883
3765
  function composeAnalysisWithOverlays(analysis, overlays) {
2884
3766
  const overlayIR = canonicalizeChainDSL(overlays);
2885
3767
  const overlayFields = collectOverlayFields(overlayIR.elements);