@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
@@ -71,6 +71,7 @@ function canonicalizeChainDSL(form) {
71
71
  kind: "form-ir",
72
72
  irVersion: import_core.IR_VERSION,
73
73
  elements: canonicalizeElements(form.elements),
74
+ rootAnnotations: [],
74
75
  typeRegistry: {},
75
76
  provenance: CHAIN_DSL_PROVENANCE
76
77
  };
@@ -376,6 +377,7 @@ function canonicalizeTSDoc(analysis, source) {
376
377
  irVersion: import_core2.IR_VERSION,
377
378
  elements,
378
379
  typeRegistry: analysis.typeRegistry,
380
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
379
381
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
380
382
  provenance
381
383
  };
@@ -436,85 +438,17 @@ function wrapInConditional(field, layout, provenance) {
436
438
  }
437
439
 
438
440
  // src/analyzer/program.ts
439
- var ts = __toESM(require("typescript"), 1);
441
+ var ts4 = __toESM(require("typescript"), 1);
440
442
  var path = __toESM(require("path"), 1);
441
- function createProgramContext(filePath) {
442
- const absolutePath = path.resolve(filePath);
443
- const fileDir = path.dirname(absolutePath);
444
- const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
445
- let compilerOptions;
446
- let fileNames;
447
- if (configPath) {
448
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
449
- if (configFile.error) {
450
- throw new Error(
451
- `Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
452
- );
453
- }
454
- const parsed = ts.parseJsonConfigFileContent(
455
- configFile.config,
456
- ts.sys,
457
- path.dirname(configPath)
458
- );
459
- if (parsed.errors.length > 0) {
460
- const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
461
- throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
462
- }
463
- compilerOptions = parsed.options;
464
- fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
465
- } else {
466
- compilerOptions = {
467
- target: ts.ScriptTarget.ES2022,
468
- module: ts.ModuleKind.NodeNext,
469
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
470
- strict: true,
471
- skipLibCheck: true,
472
- declaration: true
473
- };
474
- fileNames = [absolutePath];
475
- }
476
- const program = ts.createProgram(fileNames, compilerOptions);
477
- const sourceFile = program.getSourceFile(absolutePath);
478
- if (!sourceFile) {
479
- throw new Error(`Could not find source file: ${absolutePath}`);
480
- }
481
- return {
482
- program,
483
- checker: program.getTypeChecker(),
484
- sourceFile
485
- };
486
- }
487
- function findNodeByName(sourceFile, name, predicate, getName) {
488
- let result = null;
489
- function visit(node) {
490
- if (result) return;
491
- if (predicate(node) && getName(node) === name) {
492
- result = node;
493
- return;
494
- }
495
- ts.forEachChild(node, visit);
496
- }
497
- visit(sourceFile);
498
- return result;
499
- }
500
- function findClassByName(sourceFile, className) {
501
- return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
502
- }
503
- function findInterfaceByName(sourceFile, interfaceName) {
504
- return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
505
- }
506
- function findTypeAliasByName(sourceFile, aliasName) {
507
- return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
508
- }
509
443
 
510
444
  // src/analyzer/class-analyzer.ts
511
- var ts4 = __toESM(require("typescript"), 1);
445
+ var ts3 = __toESM(require("typescript"), 1);
512
446
 
513
447
  // src/analyzer/jsdoc-constraints.ts
514
- var ts3 = __toESM(require("typescript"), 1);
448
+ var ts2 = __toESM(require("typescript"), 1);
515
449
 
516
450
  // src/analyzer/tsdoc-parser.ts
517
- var ts2 = __toESM(require("typescript"), 1);
451
+ var ts = __toESM(require("typescript"), 1);
518
452
  var import_tsdoc = require("@microsoft/tsdoc");
519
453
  var import_core3 = require("@formspec/core");
520
454
 
@@ -542,7 +476,7 @@ var LENGTH_CONSTRAINT_MAP = {
542
476
  maxItems: "maxItems"
543
477
  };
544
478
  var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
545
- function createFormSpecTSDocConfig() {
479
+ function createFormSpecTSDocConfig(extensionTagNames = []) {
546
480
  const config = new import_tsdoc.TSDocConfiguration();
547
481
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
548
482
  config.addTagDefinition(
@@ -562,14 +496,34 @@ function createFormSpecTSDocConfig() {
562
496
  })
563
497
  );
564
498
  }
499
+ for (const tagName of extensionTagNames) {
500
+ config.addTagDefinition(
501
+ new import_tsdoc.TSDocTagDefinition({
502
+ tagName: "@" + tagName,
503
+ syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
504
+ allowMultiple: true
505
+ })
506
+ );
507
+ }
565
508
  return config;
566
509
  }
567
- var sharedParser;
568
- function getParser() {
569
- sharedParser ??= new import_tsdoc.TSDocParser(createFormSpecTSDocConfig());
570
- return sharedParser;
571
- }
572
- function parseTSDocTags(node, file = "") {
510
+ var parserCache = /* @__PURE__ */ new Map();
511
+ function getParser(options) {
512
+ const extensionTagNames = [
513
+ ...options?.extensionRegistry?.extensions.flatMap(
514
+ (extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
515
+ ) ?? []
516
+ ].sort();
517
+ const cacheKey = extensionTagNames.join("|");
518
+ const existing = parserCache.get(cacheKey);
519
+ if (existing) {
520
+ return existing;
521
+ }
522
+ const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
523
+ parserCache.set(cacheKey, parser);
524
+ return parser;
525
+ }
526
+ function parseTSDocTags(node, file = "", options) {
573
527
  const constraints = [];
574
528
  const annotations = [];
575
529
  let displayName;
@@ -580,17 +534,17 @@ function parseTSDocTags(node, file = "") {
580
534
  let placeholderProvenance;
581
535
  const sourceFile = node.getSourceFile();
582
536
  const sourceText = sourceFile.getFullText();
583
- const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
537
+ const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
584
538
  if (commentRanges) {
585
539
  for (const range of commentRanges) {
586
- if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
540
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
587
541
  continue;
588
542
  }
589
543
  const commentText = sourceText.substring(range.pos, range.end);
590
544
  if (!commentText.startsWith("/**")) {
591
545
  continue;
592
546
  }
593
- const parser = getParser();
547
+ const parser = getParser(options);
594
548
  const parserContext = parser.parseRange(
595
549
  import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
596
550
  );
@@ -601,26 +555,31 @@ function parseTSDocTags(node, file = "") {
601
555
  const text2 = extractBlockText(block).trim();
602
556
  if (text2 === "") continue;
603
557
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
604
- if (tagName === "displayName") {
605
- if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
606
- displayName = text2;
607
- displayNameProvenance = provenance2;
608
- }
609
- } else if (tagName === "format") {
610
- annotations.push({
611
- kind: "annotation",
612
- annotationKind: "format",
613
- value: text2,
614
- provenance: provenance2
615
- });
616
- } else {
617
- if (tagName === "description" && description === void 0) {
558
+ switch (tagName) {
559
+ case "displayName":
560
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
561
+ displayName = text2;
562
+ displayNameProvenance = provenance2;
563
+ }
564
+ break;
565
+ case "format":
566
+ annotations.push({
567
+ kind: "annotation",
568
+ annotationKind: "format",
569
+ value: text2,
570
+ provenance: provenance2
571
+ });
572
+ break;
573
+ case "description":
618
574
  description = text2;
619
575
  descriptionProvenance = provenance2;
620
- } else if (tagName === "placeholder" && placeholder === void 0) {
621
- placeholder = text2;
622
- placeholderProvenance = provenance2;
623
- }
576
+ break;
577
+ case "placeholder":
578
+ if (placeholder === void 0) {
579
+ placeholder = text2;
580
+ placeholderProvenance = provenance2;
581
+ }
582
+ break;
624
583
  }
625
584
  continue;
626
585
  }
@@ -629,7 +588,7 @@ function parseTSDocTags(node, file = "") {
629
588
  const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
630
589
  if (text === "" && expectedType !== "boolean") continue;
631
590
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
632
- const constraintNode = parseConstraintValue(tagName, text, provenance);
591
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
633
592
  if (constraintNode) {
634
593
  constraints.push(constraintNode);
635
594
  }
@@ -650,6 +609,13 @@ function parseTSDocTags(node, file = "") {
650
609
  descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
651
610
  }
652
611
  }
612
+ if (description === void 0) {
613
+ const summary = extractPlainText(docComment.summarySection).trim();
614
+ if (summary !== "") {
615
+ description = summary;
616
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
617
+ }
618
+ }
653
619
  }
654
620
  }
655
621
  if (displayName !== void 0 && displayNameProvenance !== void 0) {
@@ -676,7 +642,7 @@ function parseTSDocTags(node, file = "") {
676
642
  provenance: placeholderProvenance
677
643
  });
678
644
  }
679
- const jsDocTagsAll = ts2.getJSDocTags(node);
645
+ const jsDocTagsAll = ts.getJSDocTags(node);
680
646
  for (const tag of jsDocTagsAll) {
681
647
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
682
648
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
@@ -689,7 +655,7 @@ function parseTSDocTags(node, file = "") {
689
655
  annotations.push(defaultValueNode);
690
656
  continue;
691
657
  }
692
- const constraintNode = parseConstraintValue(tagName, text, provenance);
658
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
693
659
  if (constraintNode) {
694
660
  constraints.push(constraintNode);
695
661
  }
@@ -699,7 +665,7 @@ function parseTSDocTags(node, file = "") {
699
665
  function extractDisplayNameMetadata(node) {
700
666
  let displayName;
701
667
  const memberDisplayNames = /* @__PURE__ */ new Map();
702
- for (const tag of ts2.getJSDocTags(node)) {
668
+ for (const tag of ts.getJSDocTags(node)) {
703
669
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
704
670
  if (tagName !== "displayName") continue;
705
671
  const commentText = getTagCommentText(tag);
@@ -720,11 +686,11 @@ function extractDisplayNameMetadata(node) {
720
686
  }
721
687
  function extractPathTarget(text) {
722
688
  const trimmed = text.trimStart();
723
- const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
724
- if (!match?.[1] || !match[2]) return null;
689
+ const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
690
+ if (!match?.[1]) return null;
725
691
  return {
726
692
  path: { segments: [match[1]] },
727
- remainingText: match[2]
693
+ remainingText: match[2] ?? ""
728
694
  };
729
695
  }
730
696
  function extractBlockText(block) {
@@ -745,7 +711,11 @@ function extractPlainText(node) {
745
711
  }
746
712
  return result;
747
713
  }
748
- function parseConstraintValue(tagName, text, provenance) {
714
+ function parseConstraintValue(tagName, text, provenance, options) {
715
+ const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
716
+ if (customConstraint) {
717
+ return customConstraint;
718
+ }
749
719
  if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
750
720
  return null;
751
721
  }
@@ -850,6 +820,83 @@ function parseConstraintValue(tagName, text, provenance) {
850
820
  provenance
851
821
  };
852
822
  }
823
+ function parseExtensionConstraintValue(tagName, text, provenance, options) {
824
+ const pathResult = extractPathTarget(text);
825
+ const effectiveText = pathResult ? pathResult.remainingText : text;
826
+ const path2 = pathResult?.path;
827
+ const registry = options?.extensionRegistry;
828
+ if (registry === void 0) {
829
+ return null;
830
+ }
831
+ const directTag = registry.findConstraintTag(tagName);
832
+ if (directTag !== void 0) {
833
+ return makeCustomConstraintNode(
834
+ directTag.extensionId,
835
+ directTag.registration.constraintName,
836
+ directTag.registration.parseValue(effectiveText),
837
+ provenance,
838
+ path2,
839
+ registry
840
+ );
841
+ }
842
+ if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
843
+ return null;
844
+ }
845
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
846
+ if (broadenedTypeId === void 0) {
847
+ return null;
848
+ }
849
+ const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
850
+ if (broadened === void 0) {
851
+ return null;
852
+ }
853
+ return makeCustomConstraintNode(
854
+ broadened.extensionId,
855
+ broadened.registration.constraintName,
856
+ broadened.registration.parseValue(effectiveText),
857
+ provenance,
858
+ path2,
859
+ registry
860
+ );
861
+ }
862
+ function getBroadenedCustomTypeId(fieldType) {
863
+ if (fieldType?.kind === "custom") {
864
+ return fieldType.typeId;
865
+ }
866
+ if (fieldType?.kind !== "union") {
867
+ return void 0;
868
+ }
869
+ const customMembers = fieldType.members.filter(
870
+ (member) => member.kind === "custom"
871
+ );
872
+ if (customMembers.length !== 1) {
873
+ return void 0;
874
+ }
875
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
876
+ const allOtherMembersAreNull = nonCustomMembers.every(
877
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
878
+ );
879
+ const customMember = customMembers[0];
880
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
881
+ }
882
+ function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path2, registry) {
883
+ const constraintId = `${extensionId}/${constraintName}`;
884
+ const registration = registry.findConstraint(constraintId);
885
+ if (registration === void 0) {
886
+ throw new Error(
887
+ `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
888
+ );
889
+ }
890
+ return {
891
+ kind: "constraint",
892
+ constraintKind: "custom",
893
+ constraintId,
894
+ payload,
895
+ compositionRule: registration.compositionRule,
896
+ ...path2 && { path: path2 },
897
+ provenance
898
+ };
899
+ }
853
900
  function parseDefaultValueValue(text, provenance) {
854
901
  const trimmed = text.trim();
855
902
  let value;
@@ -906,33 +953,33 @@ function getTagCommentText(tag) {
906
953
  if (typeof tag.comment === "string") {
907
954
  return tag.comment;
908
955
  }
909
- return ts2.getTextOfJSDocComment(tag.comment);
956
+ return ts.getTextOfJSDocComment(tag.comment);
910
957
  }
911
958
 
912
959
  // src/analyzer/jsdoc-constraints.ts
913
- function extractJSDocConstraintNodes(node, file = "") {
914
- const result = parseTSDocTags(node, file);
960
+ function extractJSDocConstraintNodes(node, file = "", options) {
961
+ const result = parseTSDocTags(node, file, options);
915
962
  return [...result.constraints];
916
963
  }
917
- function extractJSDocAnnotationNodes(node, file = "") {
918
- const result = parseTSDocTags(node, file);
964
+ function extractJSDocAnnotationNodes(node, file = "", options) {
965
+ const result = parseTSDocTags(node, file, options);
919
966
  return [...result.annotations];
920
967
  }
921
968
  function extractDefaultValueAnnotation(initializer, file = "") {
922
969
  if (!initializer) return null;
923
970
  let value;
924
- if (ts3.isStringLiteral(initializer)) {
971
+ if (ts2.isStringLiteral(initializer)) {
925
972
  value = initializer.text;
926
- } else if (ts3.isNumericLiteral(initializer)) {
973
+ } else if (ts2.isNumericLiteral(initializer)) {
927
974
  value = Number(initializer.text);
928
- } else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
975
+ } else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
929
976
  value = true;
930
- } else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
977
+ } else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
931
978
  value = false;
932
- } else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
979
+ } else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
933
980
  value = null;
934
- } else if (ts3.isPrefixUnaryExpression(initializer)) {
935
- if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
981
+ } else if (ts2.isPrefixUnaryExpression(initializer)) {
982
+ if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
936
983
  value = -Number(initializer.operand.text);
937
984
  }
938
985
  }
@@ -954,36 +1001,56 @@ function extractDefaultValueAnnotation(initializer, file = "") {
954
1001
 
955
1002
  // src/analyzer/class-analyzer.ts
956
1003
  function isObjectType(type) {
957
- return !!(type.flags & ts4.TypeFlags.Object);
1004
+ return !!(type.flags & ts3.TypeFlags.Object);
958
1005
  }
959
1006
  function isTypeReference(type) {
960
- return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1007
+ return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
961
1008
  }
962
1009
  var RESOLVING_TYPE_PLACEHOLDER = {
963
1010
  kind: "object",
964
1011
  properties: [],
965
1012
  additionalProperties: true
966
1013
  };
967
- function analyzeClassToIR(classDecl, checker, file = "") {
1014
+ function makeParseOptions(extensionRegistry, fieldType) {
1015
+ if (extensionRegistry === void 0 && fieldType === void 0) {
1016
+ return void 0;
1017
+ }
1018
+ return {
1019
+ ...extensionRegistry !== void 0 && { extensionRegistry },
1020
+ ...fieldType !== void 0 && { fieldType }
1021
+ };
1022
+ }
1023
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
968
1024
  const name = classDecl.name?.text ?? "AnonymousClass";
969
1025
  const fields = [];
970
1026
  const fieldLayouts = [];
971
1027
  const typeRegistry = {};
972
- const annotations = extractJSDocAnnotationNodes(classDecl, file);
1028
+ const annotations = extractJSDocAnnotationNodes(
1029
+ classDecl,
1030
+ file,
1031
+ makeParseOptions(extensionRegistry)
1032
+ );
973
1033
  const visiting = /* @__PURE__ */ new Set();
974
1034
  const instanceMethods = [];
975
1035
  const staticMethods = [];
976
1036
  for (const member of classDecl.members) {
977
- if (ts4.isPropertyDeclaration(member)) {
978
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1037
+ if (ts3.isPropertyDeclaration(member)) {
1038
+ const fieldNode = analyzeFieldToIR(
1039
+ member,
1040
+ checker,
1041
+ file,
1042
+ typeRegistry,
1043
+ visiting,
1044
+ extensionRegistry
1045
+ );
979
1046
  if (fieldNode) {
980
1047
  fields.push(fieldNode);
981
1048
  fieldLayouts.push({});
982
1049
  }
983
- } else if (ts4.isMethodDeclaration(member)) {
1050
+ } else if (ts3.isMethodDeclaration(member)) {
984
1051
  const methodInfo = analyzeMethod(member, checker);
985
1052
  if (methodInfo) {
986
- const isStatic = member.modifiers?.some((m) => m.kind === ts4.SyntaxKind.StaticKeyword);
1053
+ const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
987
1054
  if (isStatic) {
988
1055
  staticMethods.push(methodInfo);
989
1056
  } else {
@@ -1002,15 +1069,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1002
1069
  staticMethods
1003
1070
  };
1004
1071
  }
1005
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1072
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
1006
1073
  const name = interfaceDecl.name.text;
1007
1074
  const fields = [];
1008
1075
  const typeRegistry = {};
1009
- const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1076
+ const annotations = extractJSDocAnnotationNodes(
1077
+ interfaceDecl,
1078
+ file,
1079
+ makeParseOptions(extensionRegistry)
1080
+ );
1010
1081
  const visiting = /* @__PURE__ */ new Set();
1011
1082
  for (const member of interfaceDecl.members) {
1012
- if (ts4.isPropertySignature(member)) {
1013
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1083
+ if (ts3.isPropertySignature(member)) {
1084
+ const fieldNode = analyzeInterfacePropertyToIR(
1085
+ member,
1086
+ checker,
1087
+ file,
1088
+ typeRegistry,
1089
+ visiting,
1090
+ extensionRegistry
1091
+ );
1014
1092
  if (fieldNode) {
1015
1093
  fields.push(fieldNode);
1016
1094
  }
@@ -1027,11 +1105,11 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1027
1105
  staticMethods: []
1028
1106
  };
1029
1107
  }
1030
- function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1031
- if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1108
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1109
+ if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1032
1110
  const sourceFile = typeAlias.getSourceFile();
1033
1111
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
1034
- const kindDesc = ts4.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1112
+ const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1035
1113
  return {
1036
1114
  ok: false,
1037
1115
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
@@ -1040,11 +1118,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1040
1118
  const name = typeAlias.name.text;
1041
1119
  const fields = [];
1042
1120
  const typeRegistry = {};
1043
- const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1121
+ const annotations = extractJSDocAnnotationNodes(
1122
+ typeAlias,
1123
+ file,
1124
+ makeParseOptions(extensionRegistry)
1125
+ );
1044
1126
  const visiting = /* @__PURE__ */ new Set();
1045
1127
  for (const member of typeAlias.type.members) {
1046
- if (ts4.isPropertySignature(member)) {
1047
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1128
+ if (ts3.isPropertySignature(member)) {
1129
+ const fieldNode = analyzeInterfacePropertyToIR(
1130
+ member,
1131
+ checker,
1132
+ file,
1133
+ typeRegistry,
1134
+ visiting,
1135
+ extensionRegistry
1136
+ );
1048
1137
  if (fieldNode) {
1049
1138
  fields.push(fieldNode);
1050
1139
  }
@@ -1063,22 +1152,36 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1063
1152
  }
1064
1153
  };
1065
1154
  }
1066
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1067
- if (!ts4.isIdentifier(prop.name)) {
1155
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1156
+ if (!ts3.isIdentifier(prop.name)) {
1068
1157
  return null;
1069
1158
  }
1070
1159
  const name = prop.name.text;
1071
1160
  const tsType = checker.getTypeAtLocation(prop);
1072
1161
  const optional = prop.questionToken !== void 0;
1073
1162
  const provenance = provenanceForNode(prop, file);
1074
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1163
+ let type = resolveTypeNode(
1164
+ tsType,
1165
+ checker,
1166
+ file,
1167
+ typeRegistry,
1168
+ visiting,
1169
+ prop,
1170
+ extensionRegistry
1171
+ );
1075
1172
  const constraints = [];
1076
- if (prop.type) {
1077
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1173
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
1174
+ constraints.push(
1175
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
1176
+ );
1078
1177
  }
1079
- constraints.push(...extractJSDocConstraintNodes(prop, file));
1178
+ constraints.push(
1179
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
1180
+ );
1080
1181
  let annotations = [];
1081
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
1182
+ annotations.push(
1183
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
1184
+ );
1082
1185
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1083
1186
  if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1084
1187
  annotations.push(defaultAnnotation);
@@ -1094,22 +1197,36 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1094
1197
  provenance
1095
1198
  };
1096
1199
  }
1097
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
1098
- if (!ts4.isIdentifier(prop.name)) {
1200
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1201
+ if (!ts3.isIdentifier(prop.name)) {
1099
1202
  return null;
1100
1203
  }
1101
1204
  const name = prop.name.text;
1102
1205
  const tsType = checker.getTypeAtLocation(prop);
1103
1206
  const optional = prop.questionToken !== void 0;
1104
1207
  const provenance = provenanceForNode(prop, file);
1105
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1208
+ let type = resolveTypeNode(
1209
+ tsType,
1210
+ checker,
1211
+ file,
1212
+ typeRegistry,
1213
+ visiting,
1214
+ prop,
1215
+ extensionRegistry
1216
+ );
1106
1217
  const constraints = [];
1107
- if (prop.type) {
1108
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1218
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
1219
+ constraints.push(
1220
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
1221
+ );
1109
1222
  }
1110
- constraints.push(...extractJSDocConstraintNodes(prop, file));
1223
+ constraints.push(
1224
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
1225
+ );
1111
1226
  let annotations = [];
1112
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
1227
+ annotations.push(
1228
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
1229
+ );
1113
1230
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1114
1231
  return {
1115
1232
  kind: "field",
@@ -1183,20 +1300,94 @@ function parseEnumMemberDisplayName(value) {
1183
1300
  if (label === "") return null;
1184
1301
  return { value: match[1], label };
1185
1302
  }
1186
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1187
- if (type.flags & ts4.TypeFlags.String) {
1303
+ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
1304
+ if (sourceNode === void 0 || extensionRegistry === void 0) {
1305
+ return null;
1306
+ }
1307
+ const typeNode = extractTypeNodeFromSource(sourceNode);
1308
+ if (typeNode === void 0) {
1309
+ return null;
1310
+ }
1311
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
1312
+ }
1313
+ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
1314
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
1315
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
1316
+ }
1317
+ const typeName = getTypeNodeRegistrationName(typeNode);
1318
+ if (typeName === null) {
1319
+ return null;
1320
+ }
1321
+ const registration = extensionRegistry.findTypeByName(typeName);
1322
+ if (registration !== void 0) {
1323
+ return {
1324
+ kind: "custom",
1325
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
1326
+ payload: null
1327
+ };
1328
+ }
1329
+ if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
1330
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
1331
+ if (aliasDecl !== void 0) {
1332
+ return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
1333
+ }
1334
+ }
1335
+ return null;
1336
+ }
1337
+ function extractTypeNodeFromSource(sourceNode) {
1338
+ if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
1339
+ return sourceNode.type;
1340
+ }
1341
+ if (ts3.isTypeNode(sourceNode)) {
1342
+ return sourceNode;
1343
+ }
1344
+ return void 0;
1345
+ }
1346
+ function getTypeNodeRegistrationName(typeNode) {
1347
+ if (ts3.isTypeReferenceNode(typeNode)) {
1348
+ return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
1349
+ }
1350
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
1351
+ return getTypeNodeRegistrationName(typeNode.type);
1352
+ }
1353
+ if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
1354
+ return typeNode.getText();
1355
+ }
1356
+ return null;
1357
+ }
1358
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1359
+ const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
1360
+ if (customType) {
1361
+ return customType;
1362
+ }
1363
+ const primitiveAlias = tryResolveNamedPrimitiveAlias(
1364
+ type,
1365
+ checker,
1366
+ file,
1367
+ typeRegistry,
1368
+ visiting,
1369
+ sourceNode,
1370
+ extensionRegistry
1371
+ );
1372
+ if (primitiveAlias) {
1373
+ return primitiveAlias;
1374
+ }
1375
+ if (type.flags & ts3.TypeFlags.String) {
1188
1376
  return { kind: "primitive", primitiveKind: "string" };
1189
1377
  }
1190
- if (type.flags & ts4.TypeFlags.Number) {
1378
+ if (type.flags & ts3.TypeFlags.Number) {
1191
1379
  return { kind: "primitive", primitiveKind: "number" };
1192
1380
  }
1193
- if (type.flags & ts4.TypeFlags.Boolean) {
1381
+ if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
1382
+ return { kind: "primitive", primitiveKind: "bigint" };
1383
+ }
1384
+ if (type.flags & ts3.TypeFlags.Boolean) {
1194
1385
  return { kind: "primitive", primitiveKind: "boolean" };
1195
1386
  }
1196
- if (type.flags & ts4.TypeFlags.Null) {
1387
+ if (type.flags & ts3.TypeFlags.Null) {
1197
1388
  return { kind: "primitive", primitiveKind: "null" };
1198
1389
  }
1199
- if (type.flags & ts4.TypeFlags.Undefined) {
1390
+ if (type.flags & ts3.TypeFlags.Undefined) {
1200
1391
  return { kind: "primitive", primitiveKind: "null" };
1201
1392
  }
1202
1393
  if (type.isStringLiteral()) {
@@ -1212,27 +1403,120 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
1212
1403
  };
1213
1404
  }
1214
1405
  if (type.isUnion()) {
1215
- return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1406
+ return resolveUnionType(
1407
+ type,
1408
+ checker,
1409
+ file,
1410
+ typeRegistry,
1411
+ visiting,
1412
+ sourceNode,
1413
+ extensionRegistry
1414
+ );
1216
1415
  }
1217
1416
  if (checker.isArrayType(type)) {
1218
- return resolveArrayType(type, checker, file, typeRegistry, visiting);
1417
+ return resolveArrayType(
1418
+ type,
1419
+ checker,
1420
+ file,
1421
+ typeRegistry,
1422
+ visiting,
1423
+ sourceNode,
1424
+ extensionRegistry
1425
+ );
1219
1426
  }
1220
1427
  if (isObjectType(type)) {
1221
- return resolveObjectType(type, checker, file, typeRegistry, visiting);
1428
+ return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
1222
1429
  }
1223
1430
  return { kind: "primitive", primitiveKind: "string" };
1224
1431
  }
1225
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
1432
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1433
+ if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
1434
+ return null;
1435
+ }
1436
+ const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
1437
+ if (!aliasDecl) {
1438
+ return null;
1439
+ }
1440
+ const aliasName = aliasDecl.name.text;
1441
+ if (!typeRegistry[aliasName]) {
1442
+ const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
1443
+ const constraints = [
1444
+ ...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
1445
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
1446
+ ];
1447
+ const annotations = extractJSDocAnnotationNodes(
1448
+ aliasDecl,
1449
+ file,
1450
+ makeParseOptions(extensionRegistry)
1451
+ );
1452
+ typeRegistry[aliasName] = {
1453
+ name: aliasName,
1454
+ type: resolveAliasedPrimitiveTarget(
1455
+ aliasType,
1456
+ checker,
1457
+ file,
1458
+ typeRegistry,
1459
+ visiting,
1460
+ extensionRegistry
1461
+ ),
1462
+ ...constraints.length > 0 && { constraints },
1463
+ ...annotations.length > 0 && { annotations },
1464
+ provenance: provenanceForDeclaration(aliasDecl, file)
1465
+ };
1466
+ }
1467
+ return { kind: "reference", name: aliasName, typeArguments: [] };
1468
+ }
1469
+ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
1470
+ const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
1471
+ if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
1472
+ return void 0;
1473
+ }
1474
+ return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
1475
+ }
1476
+ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
1477
+ if (!ts3.isTypeReferenceNode(typeNode)) {
1478
+ return false;
1479
+ }
1480
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
1481
+ if (!aliasDecl) {
1482
+ return false;
1483
+ }
1484
+ const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
1485
+ return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
1486
+ }
1487
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1488
+ const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
1489
+ if (nestedAliasDecl !== void 0) {
1490
+ return resolveAliasedPrimitiveTarget(
1491
+ checker.getTypeFromTypeNode(nestedAliasDecl.type),
1492
+ checker,
1493
+ file,
1494
+ typeRegistry,
1495
+ visiting,
1496
+ extensionRegistry
1497
+ );
1498
+ }
1499
+ return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
1500
+ }
1501
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1226
1502
  const typeName = getNamedTypeName(type);
1227
1503
  const namedDecl = getNamedTypeDeclaration(type);
1228
1504
  if (typeName && typeName in typeRegistry) {
1229
1505
  return { kind: "reference", name: typeName, typeArguments: [] };
1230
1506
  }
1231
1507
  const allTypes = type.types;
1508
+ const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
1509
+ const nonNullSourceNodes = unionMemberTypeNodes.filter(
1510
+ (memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
1511
+ );
1232
1512
  const nonNullTypes = allTypes.filter(
1233
- (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1513
+ (memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
1234
1514
  );
1235
- const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
1515
+ const nonNullMembers = nonNullTypes.map((memberType, index) => ({
1516
+ memberType,
1517
+ sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
1518
+ }));
1519
+ const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
1236
1520
  const memberDisplayNames = /* @__PURE__ */ new Map();
1237
1521
  if (namedDecl) {
1238
1522
  for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
@@ -1248,7 +1532,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1248
1532
  if (!typeName) {
1249
1533
  return result;
1250
1534
  }
1251
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
1535
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
1252
1536
  typeRegistry[typeName] = {
1253
1537
  name: typeName,
1254
1538
  type: result,
@@ -1261,7 +1545,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1261
1545
  const displayName = memberDisplayNames.get(String(value));
1262
1546
  return displayName !== void 0 ? { value, displayName } : { value };
1263
1547
  });
1264
- const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1548
+ const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
1265
1549
  if (isBooleanUnion2) {
1266
1550
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1267
1551
  const result = hasNull ? {
@@ -1296,14 +1580,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1296
1580
  } : enumNode;
1297
1581
  return registerNamed(result);
1298
1582
  }
1299
- if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1583
+ if (nonNullMembers.length === 1 && nonNullMembers[0]) {
1300
1584
  const inner = resolveTypeNode(
1301
- nonNullTypes[0],
1585
+ nonNullMembers[0].memberType,
1302
1586
  checker,
1303
1587
  file,
1304
1588
  typeRegistry,
1305
1589
  visiting,
1306
- sourceNode
1590
+ nonNullMembers[0].sourceNode ?? sourceNode,
1591
+ extensionRegistry
1307
1592
  );
1308
1593
  const result = hasNull ? {
1309
1594
  kind: "union",
@@ -1311,29 +1596,54 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1311
1596
  } : inner;
1312
1597
  return registerNamed(result);
1313
1598
  }
1314
- const members = nonNullTypes.map(
1315
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1599
+ const members = nonNullMembers.map(
1600
+ ({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
1601
+ memberType,
1602
+ checker,
1603
+ file,
1604
+ typeRegistry,
1605
+ visiting,
1606
+ memberSourceNode ?? sourceNode,
1607
+ extensionRegistry
1608
+ )
1316
1609
  );
1317
1610
  if (hasNull) {
1318
1611
  members.push({ kind: "primitive", primitiveKind: "null" });
1319
1612
  }
1320
1613
  return registerNamed({ kind: "union", members });
1321
1614
  }
1322
- function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1615
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1323
1616
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
1324
1617
  const elementType = typeArgs?.[0];
1325
- const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1618
+ const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
1619
+ const items = elementType ? resolveTypeNode(
1620
+ elementType,
1621
+ checker,
1622
+ file,
1623
+ typeRegistry,
1624
+ visiting,
1625
+ elementSourceNode,
1626
+ extensionRegistry
1627
+ ) : { kind: "primitive", primitiveKind: "string" };
1326
1628
  return { kind: "array", items };
1327
1629
  }
1328
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1630
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1329
1631
  if (type.getProperties().length > 0) {
1330
1632
  return null;
1331
1633
  }
1332
- const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
1634
+ const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
1333
1635
  if (!indexInfo) {
1334
1636
  return null;
1335
1637
  }
1336
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1638
+ const valueType = resolveTypeNode(
1639
+ indexInfo.type,
1640
+ checker,
1641
+ file,
1642
+ typeRegistry,
1643
+ visiting,
1644
+ void 0,
1645
+ extensionRegistry
1646
+ );
1337
1647
  return { kind: "record", valueType };
1338
1648
  }
1339
1649
  function typeNodeContainsReference(type, targetName) {
@@ -1361,7 +1671,7 @@ function typeNodeContainsReference(type, targetName) {
1361
1671
  }
1362
1672
  }
1363
1673
  }
1364
- function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1674
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1365
1675
  const typeName = getNamedTypeName(type);
1366
1676
  const namedTypeName = typeName ?? void 0;
1367
1677
  const namedDecl = getNamedTypeDeclaration(type);
@@ -1392,7 +1702,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1392
1702
  return { kind: "reference", name: namedTypeName, typeArguments: [] };
1393
1703
  }
1394
1704
  }
1395
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1705
+ const recordNode = tryResolveRecordType(
1706
+ type,
1707
+ checker,
1708
+ file,
1709
+ typeRegistry,
1710
+ visiting,
1711
+ extensionRegistry
1712
+ );
1396
1713
  if (recordNode) {
1397
1714
  visiting.delete(type);
1398
1715
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
@@ -1401,7 +1718,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1401
1718
  clearNamedTypeRegistration();
1402
1719
  return recordNode;
1403
1720
  }
1404
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
1721
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
1405
1722
  typeRegistry[namedTypeName] = {
1406
1723
  name: namedTypeName,
1407
1724
  type: recordNode,
@@ -1413,19 +1730,27 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1413
1730
  return recordNode;
1414
1731
  }
1415
1732
  const properties = [];
1416
- const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
1733
+ const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
1734
+ type,
1735
+ checker,
1736
+ file,
1737
+ typeRegistry,
1738
+ visiting,
1739
+ extensionRegistry
1740
+ );
1417
1741
  for (const prop of type.getProperties()) {
1418
1742
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1419
1743
  if (!declaration) continue;
1420
1744
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1421
- const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1745
+ const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
1422
1746
  const propTypeNode = resolveTypeNode(
1423
1747
  propType,
1424
1748
  checker,
1425
1749
  file,
1426
1750
  typeRegistry,
1427
1751
  visiting,
1428
- declaration
1752
+ declaration,
1753
+ extensionRegistry
1429
1754
  );
1430
1755
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1431
1756
  properties.push({
@@ -1444,7 +1769,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1444
1769
  additionalProperties: true
1445
1770
  };
1446
1771
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
1447
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
1772
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
1448
1773
  typeRegistry[namedTypeName] = {
1449
1774
  name: namedTypeName,
1450
1775
  type: objectNode,
@@ -1455,19 +1780,26 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1455
1780
  }
1456
1781
  return objectNode;
1457
1782
  }
1458
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
1783
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1459
1784
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
1460
1785
  (s) => s?.declarations != null && s.declarations.length > 0
1461
1786
  );
1462
1787
  for (const symbol of symbols) {
1463
1788
  const declarations = symbol.declarations;
1464
1789
  if (!declarations) continue;
1465
- const classDecl = declarations.find(ts4.isClassDeclaration);
1790
+ const classDecl = declarations.find(ts3.isClassDeclaration);
1466
1791
  if (classDecl) {
1467
1792
  const map = /* @__PURE__ */ new Map();
1468
1793
  for (const member of classDecl.members) {
1469
- if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
1470
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1794
+ if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
1795
+ const fieldNode = analyzeFieldToIR(
1796
+ member,
1797
+ checker,
1798
+ file,
1799
+ typeRegistry,
1800
+ visiting,
1801
+ extensionRegistry
1802
+ );
1471
1803
  if (fieldNode) {
1472
1804
  map.set(fieldNode.name, {
1473
1805
  constraints: [...fieldNode.constraints],
@@ -1479,28 +1811,86 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1479
1811
  }
1480
1812
  return map;
1481
1813
  }
1482
- const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
1814
+ const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
1483
1815
  if (interfaceDecl) {
1484
- return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
1816
+ return buildFieldNodeInfoMap(
1817
+ interfaceDecl.members,
1818
+ checker,
1819
+ file,
1820
+ typeRegistry,
1821
+ visiting,
1822
+ extensionRegistry
1823
+ );
1485
1824
  }
1486
- const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
1487
- if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
1825
+ const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
1826
+ if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
1488
1827
  return buildFieldNodeInfoMap(
1489
1828
  typeAliasDecl.type.members,
1490
1829
  checker,
1491
1830
  file,
1492
1831
  typeRegistry,
1493
- visiting
1832
+ visiting,
1833
+ extensionRegistry
1494
1834
  );
1495
1835
  }
1496
1836
  }
1497
1837
  return null;
1498
1838
  }
1499
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1839
+ function extractArrayElementTypeNode(sourceNode, checker) {
1840
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
1841
+ if (typeNode === void 0) {
1842
+ return void 0;
1843
+ }
1844
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
1845
+ if (ts3.isArrayTypeNode(resolvedTypeNode)) {
1846
+ return resolvedTypeNode.elementType;
1847
+ }
1848
+ if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
1849
+ return resolvedTypeNode.typeArguments[0];
1850
+ }
1851
+ return void 0;
1852
+ }
1853
+ function extractUnionMemberTypeNodes(sourceNode, checker) {
1854
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
1855
+ if (!typeNode) {
1856
+ return [];
1857
+ }
1858
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
1859
+ return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
1860
+ }
1861
+ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
1862
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
1863
+ return resolveAliasedTypeNode(typeNode.type, checker, visited);
1864
+ }
1865
+ if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
1866
+ return typeNode;
1867
+ }
1868
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1869
+ const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
1870
+ if (aliasDecl === void 0 || visited.has(aliasDecl)) {
1871
+ return typeNode;
1872
+ }
1873
+ visited.add(aliasDecl);
1874
+ return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
1875
+ }
1876
+ function isNullishTypeNode(typeNode) {
1877
+ if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
1878
+ return true;
1879
+ }
1880
+ return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
1881
+ }
1882
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
1500
1883
  const map = /* @__PURE__ */ new Map();
1501
1884
  for (const member of members) {
1502
- if (ts4.isPropertySignature(member)) {
1503
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1885
+ if (ts3.isPropertySignature(member)) {
1886
+ const fieldNode = analyzeInterfacePropertyToIR(
1887
+ member,
1888
+ checker,
1889
+ file,
1890
+ typeRegistry,
1891
+ visiting,
1892
+ extensionRegistry
1893
+ );
1504
1894
  if (fieldNode) {
1505
1895
  map.set(fieldNode.name, {
1506
1896
  constraints: [...fieldNode.constraints],
@@ -1513,8 +1903,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1513
1903
  return map;
1514
1904
  }
1515
1905
  var MAX_ALIAS_CHAIN_DEPTH = 8;
1516
- function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1517
- if (!ts4.isTypeReferenceNode(typeNode)) return [];
1906
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
1907
+ if (!ts3.isTypeReferenceNode(typeNode)) return [];
1518
1908
  if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
1519
1909
  const aliasName = typeNode.typeName.getText();
1520
1910
  throw new Error(
@@ -1523,11 +1913,26 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1523
1913
  }
1524
1914
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1525
1915
  if (!symbol?.declarations) return [];
1526
- const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
1916
+ const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
1527
1917
  if (!aliasDecl) return [];
1528
- if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
1529
- const constraints = extractJSDocConstraintNodes(aliasDecl, file);
1530
- constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
1918
+ if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
1919
+ const aliasFieldType = resolveTypeNode(
1920
+ checker.getTypeAtLocation(aliasDecl.type),
1921
+ checker,
1922
+ file,
1923
+ {},
1924
+ /* @__PURE__ */ new Set(),
1925
+ aliasDecl.type,
1926
+ extensionRegistry
1927
+ );
1928
+ const constraints = extractJSDocConstraintNodes(
1929
+ aliasDecl,
1930
+ file,
1931
+ makeParseOptions(extensionRegistry, aliasFieldType)
1932
+ );
1933
+ constraints.push(
1934
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
1935
+ );
1531
1936
  return constraints;
1532
1937
  }
1533
1938
  function provenanceForNode(node, file) {
@@ -1553,14 +1958,14 @@ function getNamedTypeName(type) {
1553
1958
  const symbol = type.getSymbol();
1554
1959
  if (symbol?.declarations) {
1555
1960
  const decl = symbol.declarations[0];
1556
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
1557
- const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
1961
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
1962
+ const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
1558
1963
  if (name) return name;
1559
1964
  }
1560
1965
  }
1561
1966
  const aliasSymbol = type.aliasSymbol;
1562
1967
  if (aliasSymbol?.declarations) {
1563
- const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
1968
+ const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
1564
1969
  if (aliasDecl) {
1565
1970
  return aliasDecl.name.text;
1566
1971
  }
@@ -1571,24 +1976,24 @@ function getNamedTypeDeclaration(type) {
1571
1976
  const symbol = type.getSymbol();
1572
1977
  if (symbol?.declarations) {
1573
1978
  const decl = symbol.declarations[0];
1574
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
1979
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
1575
1980
  return decl;
1576
1981
  }
1577
1982
  }
1578
1983
  const aliasSymbol = type.aliasSymbol;
1579
1984
  if (aliasSymbol?.declarations) {
1580
- return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
1985
+ return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
1581
1986
  }
1582
1987
  return void 0;
1583
1988
  }
1584
1989
  function analyzeMethod(method, checker) {
1585
- if (!ts4.isIdentifier(method.name)) {
1990
+ if (!ts3.isIdentifier(method.name)) {
1586
1991
  return null;
1587
1992
  }
1588
1993
  const name = method.name.text;
1589
1994
  const parameters = [];
1590
1995
  for (const param of method.parameters) {
1591
- if (ts4.isIdentifier(param.name)) {
1996
+ if (ts3.isIdentifier(param.name)) {
1592
1997
  const paramInfo = analyzeParameter(param, checker);
1593
1998
  parameters.push(paramInfo);
1594
1999
  }
@@ -1599,7 +2004,7 @@ function analyzeMethod(method, checker) {
1599
2004
  return { name, parameters, returnTypeNode, returnType };
1600
2005
  }
1601
2006
  function analyzeParameter(param, checker) {
1602
- const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
2007
+ const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
1603
2008
  const typeNode = param.type;
1604
2009
  const type = checker.getTypeAtLocation(param);
1605
2010
  const formSpecExportName = detectFormSpecReference(typeNode);
@@ -1608,20 +2013,90 @@ function analyzeParameter(param, checker) {
1608
2013
  }
1609
2014
  function detectFormSpecReference(typeNode) {
1610
2015
  if (!typeNode) return null;
1611
- if (!ts4.isTypeReferenceNode(typeNode)) return null;
1612
- const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
2016
+ if (!ts3.isTypeReferenceNode(typeNode)) return null;
2017
+ const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
1613
2018
  if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
1614
2019
  const typeArg = typeNode.typeArguments?.[0];
1615
- if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
1616
- if (ts4.isIdentifier(typeArg.exprName)) {
2020
+ if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
2021
+ if (ts3.isIdentifier(typeArg.exprName)) {
1617
2022
  return typeArg.exprName.text;
1618
2023
  }
1619
- if (ts4.isQualifiedName(typeArg.exprName)) {
2024
+ if (ts3.isQualifiedName(typeArg.exprName)) {
1620
2025
  return typeArg.exprName.right.text;
1621
2026
  }
1622
2027
  return null;
1623
2028
  }
1624
2029
 
2030
+ // src/analyzer/program.ts
2031
+ function createProgramContext(filePath) {
2032
+ const absolutePath = path.resolve(filePath);
2033
+ const fileDir = path.dirname(absolutePath);
2034
+ const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
2035
+ let compilerOptions;
2036
+ let fileNames;
2037
+ if (configPath) {
2038
+ const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
2039
+ if (configFile.error) {
2040
+ throw new Error(
2041
+ `Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
2042
+ );
2043
+ }
2044
+ const parsed = ts4.parseJsonConfigFileContent(
2045
+ configFile.config,
2046
+ ts4.sys,
2047
+ path.dirname(configPath)
2048
+ );
2049
+ if (parsed.errors.length > 0) {
2050
+ const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
2051
+ throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
2052
+ }
2053
+ compilerOptions = parsed.options;
2054
+ fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
2055
+ } else {
2056
+ compilerOptions = {
2057
+ target: ts4.ScriptTarget.ES2022,
2058
+ module: ts4.ModuleKind.NodeNext,
2059
+ moduleResolution: ts4.ModuleResolutionKind.NodeNext,
2060
+ strict: true,
2061
+ skipLibCheck: true,
2062
+ declaration: true
2063
+ };
2064
+ fileNames = [absolutePath];
2065
+ }
2066
+ const program = ts4.createProgram(fileNames, compilerOptions);
2067
+ const sourceFile = program.getSourceFile(absolutePath);
2068
+ if (!sourceFile) {
2069
+ throw new Error(`Could not find source file: ${absolutePath}`);
2070
+ }
2071
+ return {
2072
+ program,
2073
+ checker: program.getTypeChecker(),
2074
+ sourceFile
2075
+ };
2076
+ }
2077
+ function findNodeByName(sourceFile, name, predicate, getName) {
2078
+ let result = null;
2079
+ function visit(node) {
2080
+ if (result) return;
2081
+ if (predicate(node) && getName(node) === name) {
2082
+ result = node;
2083
+ return;
2084
+ }
2085
+ ts4.forEachChild(node, visit);
2086
+ }
2087
+ visit(sourceFile);
2088
+ return result;
2089
+ }
2090
+ function findClassByName(sourceFile, className) {
2091
+ return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
2092
+ }
2093
+ function findInterfaceByName(sourceFile, interfaceName) {
2094
+ return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
2095
+ }
2096
+ function findTypeAliasByName(sourceFile, aliasName) {
2097
+ return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
2098
+ }
2099
+
1625
2100
  // src/json-schema/ir-generator.ts
1626
2101
  function makeContext(options) {
1627
2102
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -1640,6 +2115,9 @@ function generateJsonSchemaFromIR(ir, options) {
1640
2115
  const ctx = makeContext(options);
1641
2116
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
1642
2117
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
2118
+ if (typeDef.constraints && typeDef.constraints.length > 0) {
2119
+ applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
2120
+ }
1643
2121
  if (typeDef.annotations && typeDef.annotations.length > 0) {
1644
2122
  applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
1645
2123
  }
@@ -1808,7 +2286,9 @@ function generateTypeNode(type, ctx) {
1808
2286
  }
1809
2287
  }
1810
2288
  function generatePrimitiveType(type) {
1811
- return { type: type.primitiveKind };
2289
+ return {
2290
+ type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
2291
+ };
1812
2292
  }
1813
2293
  function generateEnumType(type) {
1814
2294
  const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
@@ -1981,7 +2461,7 @@ function applyAnnotations(schema, annotations, ctx) {
1981
2461
  case "deprecated":
1982
2462
  schema.deprecated = true;
1983
2463
  if (annotation.message !== void 0 && annotation.message !== "") {
1984
- schema["x-formspec-deprecation-description"] = annotation.message;
2464
+ schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
1985
2465
  }
1986
2466
  break;
1987
2467
  case "placeholder":
@@ -2014,7 +2494,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
2014
2494
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
2015
2495
  );
2016
2496
  }
2017
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
2497
+ assignVendorPrefixedExtensionKeywords(
2498
+ schema,
2499
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
2500
+ ctx.vendorPrefix,
2501
+ `custom constraint "${constraint.constraintId}"`
2502
+ );
2018
2503
  }
2019
2504
  function applyCustomAnnotation(schema, annotation, ctx) {
2020
2505
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -2026,7 +2511,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
2026
2511
  if (registration.toJsonSchema === void 0) {
2027
2512
  return;
2028
2513
  }
2029
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
2514
+ assignVendorPrefixedExtensionKeywords(
2515
+ schema,
2516
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
2517
+ ctx.vendorPrefix,
2518
+ `custom annotation "${annotation.annotationId}"`
2519
+ );
2520
+ }
2521
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
2522
+ for (const [key, value] of Object.entries(extensionSchema)) {
2523
+ if (!key.startsWith(`${vendorPrefix}-`)) {
2524
+ throw new Error(
2525
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
2526
+ );
2527
+ }
2528
+ schema[key] = value;
2529
+ }
2030
2530
  }
2031
2531
 
2032
2532
  // src/ui-schema/schema.ts
@@ -2252,16 +2752,8 @@ function generateUiSchemaFromIR(ir) {
2252
2752
  return parseOrThrow(uiSchema, result, "UI Schema");
2253
2753
  }
2254
2754
 
2255
- // src/generators/class-schema.ts
2256
- function generateClassSchemas(analysis, source) {
2257
- const ir = canonicalizeTSDoc(analysis, source);
2258
- return {
2259
- jsonSchema: generateJsonSchemaFromIR(ir),
2260
- uiSchema: generateUiSchemaFromIR(ir)
2261
- };
2262
- }
2263
-
2264
2755
  // src/validate/constraint-validator.ts
2756
+ var import_core4 = require("@formspec/core");
2265
2757
  function addContradiction(ctx, message, primary, related) {
2266
2758
  ctx.diagnostics.push({
2267
2759
  code: "CONTRADICTING_CONSTRAINTS",
@@ -2307,6 +2799,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
2307
2799
  relatedLocations: [related]
2308
2800
  });
2309
2801
  }
2802
+ function getExtensionIdFromConstraintId(constraintId) {
2803
+ const separator = constraintId.lastIndexOf("/");
2804
+ if (separator <= 0) {
2805
+ return null;
2806
+ }
2807
+ return constraintId.slice(0, separator);
2808
+ }
2310
2809
  function findNumeric(constraints, constraintKind) {
2311
2810
  return constraints.find((c) => c.constraintKind === constraintKind);
2312
2811
  }
@@ -2477,6 +2976,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
2477
2976
  strongestByKey.set(key, constraint);
2478
2977
  }
2479
2978
  }
2979
+ function compareCustomConstraintStrength(current, previous) {
2980
+ const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
2981
+ const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
2982
+ switch (current.role.bound) {
2983
+ case "lower":
2984
+ return equalPayloadTiebreaker;
2985
+ case "upper":
2986
+ return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
2987
+ case "exact":
2988
+ return order === 0 ? 0 : Number.NaN;
2989
+ default: {
2990
+ const _exhaustive = current.role.bound;
2991
+ return _exhaustive;
2992
+ }
2993
+ }
2994
+ }
2995
+ function compareSemanticInclusivity(currentInclusive, previousInclusive) {
2996
+ if (currentInclusive === previousInclusive) {
2997
+ return 0;
2998
+ }
2999
+ return currentInclusive ? -1 : 1;
3000
+ }
3001
+ function customConstraintsContradict(lower, upper) {
3002
+ const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
3003
+ if (order > 0) {
3004
+ return true;
3005
+ }
3006
+ if (order < 0) {
3007
+ return false;
3008
+ }
3009
+ return !lower.role.inclusive || !upper.role.inclusive;
3010
+ }
3011
+ function describeCustomConstraintTag(constraint) {
3012
+ return constraint.provenance.tagName ?? constraint.constraintId;
3013
+ }
3014
+ function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
3015
+ if (ctx.extensionRegistry === void 0) {
3016
+ return;
3017
+ }
3018
+ const strongestByKey = /* @__PURE__ */ new Map();
3019
+ const lowerByFamily = /* @__PURE__ */ new Map();
3020
+ const upperByFamily = /* @__PURE__ */ new Map();
3021
+ for (const constraint of constraints) {
3022
+ if (constraint.constraintKind !== "custom") {
3023
+ continue;
3024
+ }
3025
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3026
+ if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
3027
+ continue;
3028
+ }
3029
+ const entry = {
3030
+ constraint,
3031
+ comparePayloads: registration.comparePayloads,
3032
+ role: registration.semanticRole
3033
+ };
3034
+ const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
3035
+ const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
3036
+ const previous = strongestByKey.get(boundKey);
3037
+ if (previous !== void 0) {
3038
+ const strength = compareCustomConstraintStrength(entry, previous);
3039
+ if (Number.isNaN(strength)) {
3040
+ addContradiction(
3041
+ ctx,
3042
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
3043
+ constraint.provenance,
3044
+ previous.constraint.provenance
3045
+ );
3046
+ continue;
3047
+ }
3048
+ if (strength < 0) {
3049
+ addConstraintBroadening(
3050
+ ctx,
3051
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
3052
+ constraint.provenance,
3053
+ previous.constraint.provenance
3054
+ );
3055
+ continue;
3056
+ }
3057
+ if (strength > 0) {
3058
+ strongestByKey.set(boundKey, entry);
3059
+ }
3060
+ } else {
3061
+ strongestByKey.set(boundKey, entry);
3062
+ }
3063
+ if (registration.semanticRole.bound === "lower") {
3064
+ lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3065
+ } else if (registration.semanticRole.bound === "upper") {
3066
+ upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3067
+ }
3068
+ }
3069
+ for (const [familyKey, lower] of lowerByFamily) {
3070
+ const upper = upperByFamily.get(familyKey);
3071
+ if (upper === void 0) {
3072
+ continue;
3073
+ }
3074
+ if (!customConstraintsContradict(lower, upper)) {
3075
+ continue;
3076
+ }
3077
+ addContradiction(
3078
+ ctx,
3079
+ `Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
3080
+ lower.constraint.provenance,
3081
+ upper.constraint.provenance
3082
+ );
3083
+ }
3084
+ }
2480
3085
  function checkNumericContradictions(ctx, fieldName, constraints) {
2481
3086
  const min = findNumeric(constraints, "minimum");
2482
3087
  const max = findNumeric(constraints, "maximum");
@@ -2624,6 +3229,26 @@ function dereferenceType(ctx, type) {
2624
3229
  }
2625
3230
  return current;
2626
3231
  }
3232
+ function collectReferencedTypeConstraints(ctx, type) {
3233
+ const collected = [];
3234
+ let current = type;
3235
+ const seen = /* @__PURE__ */ new Set();
3236
+ while (current.kind === "reference") {
3237
+ if (seen.has(current.name)) {
3238
+ break;
3239
+ }
3240
+ seen.add(current.name);
3241
+ const definition = ctx.typeRegistry[current.name];
3242
+ if (definition === void 0) {
3243
+ break;
3244
+ }
3245
+ if (definition.constraints !== void 0) {
3246
+ collected.push(...definition.constraints);
3247
+ }
3248
+ current = definition.type;
3249
+ }
3250
+ return collected;
3251
+ }
2627
3252
  function resolvePathTargetType(ctx, type, segments) {
2628
3253
  const effectiveType = dereferenceType(ctx, type);
2629
3254
  if (segments.length === 0) {
@@ -2645,12 +3270,33 @@ function resolvePathTargetType(ctx, type, segments) {
2645
3270
  }
2646
3271
  return { kind: "unresolvable", type: effectiveType };
2647
3272
  }
3273
+ function isNullType(type) {
3274
+ return type.kind === "primitive" && type.primitiveKind === "null";
3275
+ }
3276
+ function collectCustomConstraintCandidateTypes(ctx, type) {
3277
+ const effectiveType = dereferenceType(ctx, type);
3278
+ const candidates = [effectiveType];
3279
+ if (effectiveType.kind === "array") {
3280
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
3281
+ }
3282
+ if (effectiveType.kind === "union") {
3283
+ const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
3284
+ const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
3285
+ if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
3286
+ const [nullableMember] = nonNullMembers;
3287
+ if (nullableMember !== void 0) {
3288
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
3289
+ }
3290
+ }
3291
+ }
3292
+ return candidates;
3293
+ }
2648
3294
  function formatPathTargetFieldName(fieldName, path2) {
2649
3295
  return path2.length === 0 ? fieldName : `${fieldName}.${path2.join(".")}`;
2650
3296
  }
2651
3297
  function checkConstraintOnType(ctx, fieldName, type, constraint) {
2652
3298
  const effectiveType = dereferenceType(ctx, type);
2653
- const isNumber = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "number";
3299
+ const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
2654
3300
  const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
2655
3301
  const isArray = effectiveType.kind === "array";
2656
3302
  const isEnum = effectiveType.kind === "enum";
@@ -2708,7 +3354,9 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
2708
3354
  break;
2709
3355
  }
2710
3356
  case "const": {
2711
- const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
3357
+ const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
3358
+ effectiveType.primitiveKind
3359
+ ) || effectiveType.kind === "enum";
2712
3360
  if (!isPrimitiveConstType) {
2713
3361
  addTypeMismatch(
2714
3362
  ctx,
@@ -2719,7 +3367,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
2719
3367
  }
2720
3368
  if (effectiveType.kind === "primitive") {
2721
3369
  const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
2722
- if (valueType !== effectiveType.primitiveKind) {
3370
+ const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
3371
+ if (valueType !== expectedValueType) {
2723
3372
  addTypeMismatch(
2724
3373
  ctx,
2725
3374
  `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
@@ -2788,8 +3437,37 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
2788
3437
  );
2789
3438
  return;
2790
3439
  }
2791
- if (registration.applicableTypes === null) return;
2792
- if (!registration.applicableTypes.includes(type.kind)) {
3440
+ const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
3441
+ const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core4.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
3442
+ if (normalizedTagName !== void 0) {
3443
+ const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
3444
+ const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
3445
+ if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
3446
+ (candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
3447
+ )) {
3448
+ addTypeMismatch(
3449
+ ctx,
3450
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3451
+ constraint.provenance
3452
+ );
3453
+ return;
3454
+ }
3455
+ }
3456
+ if (registration.applicableTypes === null) {
3457
+ if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
3458
+ addTypeMismatch(
3459
+ ctx,
3460
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3461
+ constraint.provenance
3462
+ );
3463
+ }
3464
+ return;
3465
+ }
3466
+ const applicableTypes = registration.applicableTypes;
3467
+ const matchesApplicableType = candidateTypes.some(
3468
+ (candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
3469
+ );
3470
+ if (!matchesApplicableType) {
2793
3471
  addTypeMismatch(
2794
3472
  ctx,
2795
3473
  `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
@@ -2798,7 +3476,10 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
2798
3476
  }
2799
3477
  }
2800
3478
  function validateFieldNode(ctx, field) {
2801
- validateConstraints(ctx, field.name, field.type, field.constraints);
3479
+ validateConstraints(ctx, field.name, field.type, [
3480
+ ...collectReferencedTypeConstraints(ctx, field.type),
3481
+ ...field.constraints
3482
+ ]);
2802
3483
  if (field.type.kind === "object") {
2803
3484
  for (const prop of field.type.properties) {
2804
3485
  validateObjectProperty(ctx, field.name, prop);
@@ -2807,7 +3488,10 @@ function validateFieldNode(ctx, field) {
2807
3488
  }
2808
3489
  function validateObjectProperty(ctx, parentName, prop) {
2809
3490
  const qualifiedName = `${parentName}.${prop.name}`;
2810
- validateConstraints(ctx, qualifiedName, prop.type, prop.constraints);
3491
+ validateConstraints(ctx, qualifiedName, prop.type, [
3492
+ ...collectReferencedTypeConstraints(ctx, prop.type),
3493
+ ...prop.constraints
3494
+ ]);
2811
3495
  if (prop.type.kind === "object") {
2812
3496
  for (const nestedProp of prop.type.properties) {
2813
3497
  validateObjectProperty(ctx, qualifiedName, nestedProp);
@@ -2820,6 +3504,7 @@ function validateConstraints(ctx, name, type, constraints) {
2820
3504
  checkAllowedMembersContradiction(ctx, name, constraints);
2821
3505
  checkConstContradictions(ctx, name, constraints);
2822
3506
  checkConstraintBroadening(ctx, name, constraints);
3507
+ checkCustomConstraintSemantics(ctx, name, constraints);
2823
3508
  checkTypeApplicability(ctx, name, type, constraints);
2824
3509
  }
2825
3510
  function validateElement(ctx, element) {
@@ -2858,10 +3543,43 @@ function validateIR(ir, options) {
2858
3543
  };
2859
3544
  }
2860
3545
 
3546
+ // src/generators/class-schema.ts
3547
+ function generateClassSchemas(analysis, source, options) {
3548
+ const ir = canonicalizeTSDoc(analysis, source);
3549
+ const validationResult = validateIR(ir, {
3550
+ ...options?.extensionRegistry !== void 0 && {
3551
+ extensionRegistry: options.extensionRegistry
3552
+ },
3553
+ ...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
3554
+ });
3555
+ if (!validationResult.valid) {
3556
+ throw new Error(formatValidationError(validationResult.diagnostics));
3557
+ }
3558
+ return {
3559
+ jsonSchema: generateJsonSchemaFromIR(ir, options),
3560
+ uiSchema: generateUiSchemaFromIR(ir)
3561
+ };
3562
+ }
3563
+ function formatValidationError(diagnostics) {
3564
+ const lines = diagnostics.map((diagnostic) => {
3565
+ const primary = formatLocation(diagnostic.primaryLocation);
3566
+ const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
3567
+ return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
3568
+ });
3569
+ return `FormSpec validation failed:
3570
+ ${lines.map((line) => `- ${line}`).join("\n")}`;
3571
+ }
3572
+ function formatLocation(location) {
3573
+ return `${location.file}:${String(location.line)}:${String(location.column)}`;
3574
+ }
3575
+
2861
3576
  // src/extensions/registry.ts
2862
3577
  function createExtensionRegistry(extensions) {
2863
3578
  const typeMap = /* @__PURE__ */ new Map();
3579
+ const typeNameMap = /* @__PURE__ */ new Map();
2864
3580
  const constraintMap = /* @__PURE__ */ new Map();
3581
+ const constraintTagMap = /* @__PURE__ */ new Map();
3582
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
2865
3583
  const annotationMap = /* @__PURE__ */ new Map();
2866
3584
  for (const ext of extensions) {
2867
3585
  if (ext.types !== void 0) {
@@ -2871,6 +3589,27 @@ function createExtensionRegistry(extensions) {
2871
3589
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
2872
3590
  }
2873
3591
  typeMap.set(qualifiedId, type);
3592
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
3593
+ if (typeNameMap.has(sourceTypeName)) {
3594
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
3595
+ }
3596
+ typeNameMap.set(sourceTypeName, {
3597
+ extensionId: ext.extensionId,
3598
+ registration: type
3599
+ });
3600
+ }
3601
+ if (type.builtinConstraintBroadenings !== void 0) {
3602
+ for (const broadening of type.builtinConstraintBroadenings) {
3603
+ const key = `${qualifiedId}:${broadening.tagName}`;
3604
+ if (builtinBroadeningMap.has(key)) {
3605
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
3606
+ }
3607
+ builtinBroadeningMap.set(key, {
3608
+ extensionId: ext.extensionId,
3609
+ registration: broadening
3610
+ });
3611
+ }
3612
+ }
2874
3613
  }
2875
3614
  }
2876
3615
  if (ext.constraints !== void 0) {
@@ -2882,6 +3621,17 @@ function createExtensionRegistry(extensions) {
2882
3621
  constraintMap.set(qualifiedId, constraint);
2883
3622
  }
2884
3623
  }
3624
+ if (ext.constraintTags !== void 0) {
3625
+ for (const tag of ext.constraintTags) {
3626
+ if (constraintTagMap.has(tag.tagName)) {
3627
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
3628
+ }
3629
+ constraintTagMap.set(tag.tagName, {
3630
+ extensionId: ext.extensionId,
3631
+ registration: tag
3632
+ });
3633
+ }
3634
+ }
2885
3635
  if (ext.annotations !== void 0) {
2886
3636
  for (const annotation of ext.annotations) {
2887
3637
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -2895,13 +3645,16 @@ function createExtensionRegistry(extensions) {
2895
3645
  return {
2896
3646
  extensions,
2897
3647
  findType: (typeId) => typeMap.get(typeId),
3648
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
2898
3649
  findConstraint: (constraintId) => constraintMap.get(constraintId),
3650
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
3651
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
2899
3652
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
2900
3653
  };
2901
3654
  }
2902
3655
 
2903
3656
  // src/generators/method-schema.ts
2904
- var import_core4 = require("@formspec/core");
3657
+ var import_core5 = require("@formspec/core");
2905
3658
  function typeToJsonSchema(type, checker) {
2906
3659
  const typeRegistry = {};
2907
3660
  const visiting = /* @__PURE__ */ new Set();
@@ -2909,7 +3662,7 @@ function typeToJsonSchema(type, checker) {
2909
3662
  const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
2910
3663
  const ir = {
2911
3664
  kind: "form-ir",
2912
- irVersion: import_core4.IR_VERSION,
3665
+ irVersion: import_core5.IR_VERSION,
2913
3666
  elements: [
2914
3667
  {
2915
3668
  kind: "field",
@@ -2921,6 +3674,7 @@ function typeToJsonSchema(type, checker) {
2921
3674
  provenance: fieldProvenance
2922
3675
  }
2923
3676
  ],
3677
+ rootAnnotations: [],
2924
3678
  typeRegistry,
2925
3679
  provenance: fieldProvenance
2926
3680
  };