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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +74 -128
  2. package/dist/__tests__/class-schema.test.d.ts +2 -0
  3. package/dist/__tests__/class-schema.test.d.ts.map +1 -0
  4. package/dist/__tests__/date-extension.integration.test.d.ts +2 -0
  5. package/dist/__tests__/date-extension.integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures/class-schema-regressions.d.ts +83 -0
  7. package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -0
  8. package/dist/__tests__/fixtures/example-date-extension.d.ts +12 -0
  9. package/dist/__tests__/fixtures/example-date-extension.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
  11. package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
  12. package/dist/__tests__/fixtures/extension-forms.d.ts +7 -0
  13. package/dist/__tests__/fixtures/extension-forms.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +1 -0
  15. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -1
  16. package/dist/__tests__/fixtures/named-primitive-aliases.d.ts +15 -0
  17. package/dist/__tests__/fixtures/named-primitive-aliases.d.ts.map +1 -0
  18. package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts +14 -0
  19. package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts.map +1 -0
  20. package/dist/__tests__/fixtures/sample-forms.d.ts +10 -0
  21. package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -1
  22. package/dist/__tests__/generate-schemas.test.d.ts +2 -0
  23. package/dist/__tests__/generate-schemas.test.d.ts.map +1 -0
  24. package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
  25. package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
  26. package/dist/__tests__/parity/parity.test.d.ts +6 -2
  27. package/dist/__tests__/parity/parity.test.d.ts.map +1 -1
  28. package/dist/__tests__/parity/utils.d.ts +9 -4
  29. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  30. package/dist/analyzer/class-analyzer.d.ts +5 -4
  31. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  32. package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
  33. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  34. package/dist/analyzer/program.d.ts +15 -0
  35. package/dist/analyzer/program.d.ts.map +1 -1
  36. package/dist/analyzer/tsdoc-parser.d.ts +23 -2
  37. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  38. package/dist/browser.cjs +269 -11
  39. package/dist/browser.cjs.map +1 -1
  40. package/dist/browser.js +269 -11
  41. package/dist/browser.js.map +1 -1
  42. package/dist/build.d.ts +28 -2
  43. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  44. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  45. package/dist/cli.cjs +1640 -282
  46. package/dist/cli.cjs.map +1 -1
  47. package/dist/cli.js +1638 -281
  48. package/dist/cli.js.map +1 -1
  49. package/dist/extensions/registry.d.ts +25 -1
  50. package/dist/extensions/registry.d.ts.map +1 -1
  51. package/dist/generators/class-schema.d.ts +4 -4
  52. package/dist/generators/class-schema.d.ts.map +1 -1
  53. package/dist/generators/method-schema.d.ts.map +1 -1
  54. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  55. package/dist/index.cjs +1615 -271
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.js +1615 -271
  58. package/dist/index.js.map +1 -1
  59. package/dist/internals.cjs +990 -236
  60. package/dist/internals.cjs.map +1 -1
  61. package/dist/internals.js +988 -234
  62. package/dist/internals.js.map +1 -1
  63. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  64. package/dist/validate/constraint-validator.d.ts.map +1 -1
  65. package/package.json +3 -3
package/dist/internals.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
  };
@@ -385,85 +387,17 @@ function wrapInConditional(field, layout, provenance) {
385
387
  }
386
388
 
387
389
  // src/analyzer/program.ts
388
- import * as ts from "typescript";
390
+ import * as ts4 from "typescript";
389
391
  import * as path from "path";
390
- function createProgramContext(filePath) {
391
- const absolutePath = path.resolve(filePath);
392
- const fileDir = path.dirname(absolutePath);
393
- const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
394
- let compilerOptions;
395
- let fileNames;
396
- if (configPath) {
397
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
398
- if (configFile.error) {
399
- throw new Error(
400
- `Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
401
- );
402
- }
403
- const parsed = ts.parseJsonConfigFileContent(
404
- configFile.config,
405
- ts.sys,
406
- path.dirname(configPath)
407
- );
408
- if (parsed.errors.length > 0) {
409
- const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
410
- throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
411
- }
412
- compilerOptions = parsed.options;
413
- fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
414
- } else {
415
- compilerOptions = {
416
- target: ts.ScriptTarget.ES2022,
417
- module: ts.ModuleKind.NodeNext,
418
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
419
- strict: true,
420
- skipLibCheck: true,
421
- declaration: true
422
- };
423
- fileNames = [absolutePath];
424
- }
425
- const program = ts.createProgram(fileNames, compilerOptions);
426
- const sourceFile = program.getSourceFile(absolutePath);
427
- if (!sourceFile) {
428
- throw new Error(`Could not find source file: ${absolutePath}`);
429
- }
430
- return {
431
- program,
432
- checker: program.getTypeChecker(),
433
- sourceFile
434
- };
435
- }
436
- function findNodeByName(sourceFile, name, predicate, getName) {
437
- let result = null;
438
- function visit(node) {
439
- if (result) return;
440
- if (predicate(node) && getName(node) === name) {
441
- result = node;
442
- return;
443
- }
444
- ts.forEachChild(node, visit);
445
- }
446
- visit(sourceFile);
447
- return result;
448
- }
449
- function findClassByName(sourceFile, className) {
450
- return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
451
- }
452
- function findInterfaceByName(sourceFile, interfaceName) {
453
- return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
454
- }
455
- function findTypeAliasByName(sourceFile, aliasName) {
456
- return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
457
- }
458
392
 
459
393
  // src/analyzer/class-analyzer.ts
460
- import * as ts4 from "typescript";
394
+ import * as ts3 from "typescript";
461
395
 
462
396
  // src/analyzer/jsdoc-constraints.ts
463
- import * as ts3 from "typescript";
397
+ import * as ts2 from "typescript";
464
398
 
465
399
  // src/analyzer/tsdoc-parser.ts
466
- import * as ts2 from "typescript";
400
+ import * as ts from "typescript";
467
401
  import {
468
402
  TSDocParser,
469
403
  TSDocConfiguration,
@@ -503,7 +437,7 @@ var LENGTH_CONSTRAINT_MAP = {
503
437
  maxItems: "maxItems"
504
438
  };
505
439
  var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
506
- function createFormSpecTSDocConfig() {
440
+ function createFormSpecTSDocConfig(extensionTagNames = []) {
507
441
  const config = new TSDocConfiguration();
508
442
  for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
509
443
  config.addTagDefinition(
@@ -523,14 +457,34 @@ function createFormSpecTSDocConfig() {
523
457
  })
524
458
  );
525
459
  }
460
+ for (const tagName of extensionTagNames) {
461
+ config.addTagDefinition(
462
+ new TSDocTagDefinition({
463
+ tagName: "@" + tagName,
464
+ syntaxKind: TSDocTagSyntaxKind.BlockTag,
465
+ allowMultiple: true
466
+ })
467
+ );
468
+ }
526
469
  return config;
527
470
  }
528
- var sharedParser;
529
- function getParser() {
530
- sharedParser ??= new TSDocParser(createFormSpecTSDocConfig());
531
- return sharedParser;
532
- }
533
- function parseTSDocTags(node, file = "") {
471
+ var parserCache = /* @__PURE__ */ new Map();
472
+ function getParser(options) {
473
+ const extensionTagNames = [
474
+ ...options?.extensionRegistry?.extensions.flatMap(
475
+ (extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
476
+ ) ?? []
477
+ ].sort();
478
+ const cacheKey = extensionTagNames.join("|");
479
+ const existing = parserCache.get(cacheKey);
480
+ if (existing) {
481
+ return existing;
482
+ }
483
+ const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
484
+ parserCache.set(cacheKey, parser);
485
+ return parser;
486
+ }
487
+ function parseTSDocTags(node, file = "", options) {
534
488
  const constraints = [];
535
489
  const annotations = [];
536
490
  let displayName;
@@ -541,17 +495,17 @@ function parseTSDocTags(node, file = "") {
541
495
  let placeholderProvenance;
542
496
  const sourceFile = node.getSourceFile();
543
497
  const sourceText = sourceFile.getFullText();
544
- const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
498
+ const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
545
499
  if (commentRanges) {
546
500
  for (const range of commentRanges) {
547
- if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
501
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
548
502
  continue;
549
503
  }
550
504
  const commentText = sourceText.substring(range.pos, range.end);
551
505
  if (!commentText.startsWith("/**")) {
552
506
  continue;
553
507
  }
554
- const parser = getParser();
508
+ const parser = getParser(options);
555
509
  const parserContext = parser.parseRange(
556
510
  TextRange.fromStringRange(sourceText, range.pos, range.end)
557
511
  );
@@ -562,26 +516,31 @@ function parseTSDocTags(node, file = "") {
562
516
  const text2 = extractBlockText(block).trim();
563
517
  if (text2 === "") continue;
564
518
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
565
- if (tagName === "displayName") {
566
- if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
567
- displayName = text2;
568
- displayNameProvenance = provenance2;
569
- }
570
- } else if (tagName === "format") {
571
- annotations.push({
572
- kind: "annotation",
573
- annotationKind: "format",
574
- value: text2,
575
- provenance: provenance2
576
- });
577
- } else {
578
- if (tagName === "description" && description === void 0) {
519
+ switch (tagName) {
520
+ case "displayName":
521
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
522
+ displayName = text2;
523
+ displayNameProvenance = provenance2;
524
+ }
525
+ break;
526
+ case "format":
527
+ annotations.push({
528
+ kind: "annotation",
529
+ annotationKind: "format",
530
+ value: text2,
531
+ provenance: provenance2
532
+ });
533
+ break;
534
+ case "description":
579
535
  description = text2;
580
536
  descriptionProvenance = provenance2;
581
- } else if (tagName === "placeholder" && placeholder === void 0) {
582
- placeholder = text2;
583
- placeholderProvenance = provenance2;
584
- }
537
+ break;
538
+ case "placeholder":
539
+ if (placeholder === void 0) {
540
+ placeholder = text2;
541
+ placeholderProvenance = provenance2;
542
+ }
543
+ break;
585
544
  }
586
545
  continue;
587
546
  }
@@ -590,7 +549,7 @@ function parseTSDocTags(node, file = "") {
590
549
  const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
591
550
  if (text === "" && expectedType !== "boolean") continue;
592
551
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
593
- const constraintNode = parseConstraintValue(tagName, text, provenance);
552
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
594
553
  if (constraintNode) {
595
554
  constraints.push(constraintNode);
596
555
  }
@@ -611,6 +570,13 @@ function parseTSDocTags(node, file = "") {
611
570
  descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
612
571
  }
613
572
  }
573
+ if (description === void 0) {
574
+ const summary = extractPlainText(docComment.summarySection).trim();
575
+ if (summary !== "") {
576
+ description = summary;
577
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
578
+ }
579
+ }
614
580
  }
615
581
  }
616
582
  if (displayName !== void 0 && displayNameProvenance !== void 0) {
@@ -637,7 +603,7 @@ function parseTSDocTags(node, file = "") {
637
603
  provenance: placeholderProvenance
638
604
  });
639
605
  }
640
- const jsDocTagsAll = ts2.getJSDocTags(node);
606
+ const jsDocTagsAll = ts.getJSDocTags(node);
641
607
  for (const tag of jsDocTagsAll) {
642
608
  const tagName = normalizeConstraintTagName(tag.tagName.text);
643
609
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
@@ -650,7 +616,7 @@ function parseTSDocTags(node, file = "") {
650
616
  annotations.push(defaultValueNode);
651
617
  continue;
652
618
  }
653
- const constraintNode = parseConstraintValue(tagName, text, provenance);
619
+ const constraintNode = parseConstraintValue(tagName, text, provenance, options);
654
620
  if (constraintNode) {
655
621
  constraints.push(constraintNode);
656
622
  }
@@ -660,7 +626,7 @@ function parseTSDocTags(node, file = "") {
660
626
  function extractDisplayNameMetadata(node) {
661
627
  let displayName;
662
628
  const memberDisplayNames = /* @__PURE__ */ new Map();
663
- for (const tag of ts2.getJSDocTags(node)) {
629
+ for (const tag of ts.getJSDocTags(node)) {
664
630
  const tagName = normalizeConstraintTagName(tag.tagName.text);
665
631
  if (tagName !== "displayName") continue;
666
632
  const commentText = getTagCommentText(tag);
@@ -681,11 +647,11 @@ function extractDisplayNameMetadata(node) {
681
647
  }
682
648
  function extractPathTarget(text) {
683
649
  const trimmed = text.trimStart();
684
- const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
685
- if (!match?.[1] || !match[2]) return null;
650
+ const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
651
+ if (!match?.[1]) return null;
686
652
  return {
687
653
  path: { segments: [match[1]] },
688
- remainingText: match[2]
654
+ remainingText: match[2] ?? ""
689
655
  };
690
656
  }
691
657
  function extractBlockText(block) {
@@ -706,7 +672,11 @@ function extractPlainText(node) {
706
672
  }
707
673
  return result;
708
674
  }
709
- function parseConstraintValue(tagName, text, provenance) {
675
+ function parseConstraintValue(tagName, text, provenance, options) {
676
+ const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
677
+ if (customConstraint) {
678
+ return customConstraint;
679
+ }
710
680
  if (!isBuiltinConstraintName(tagName)) {
711
681
  return null;
712
682
  }
@@ -811,6 +781,83 @@ function parseConstraintValue(tagName, text, provenance) {
811
781
  provenance
812
782
  };
813
783
  }
784
+ function parseExtensionConstraintValue(tagName, text, provenance, options) {
785
+ const pathResult = extractPathTarget(text);
786
+ const effectiveText = pathResult ? pathResult.remainingText : text;
787
+ const path2 = pathResult?.path;
788
+ const registry = options?.extensionRegistry;
789
+ if (registry === void 0) {
790
+ return null;
791
+ }
792
+ const directTag = registry.findConstraintTag(tagName);
793
+ if (directTag !== void 0) {
794
+ return makeCustomConstraintNode(
795
+ directTag.extensionId,
796
+ directTag.registration.constraintName,
797
+ directTag.registration.parseValue(effectiveText),
798
+ provenance,
799
+ path2,
800
+ registry
801
+ );
802
+ }
803
+ if (!isBuiltinConstraintName(tagName)) {
804
+ return null;
805
+ }
806
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
807
+ if (broadenedTypeId === void 0) {
808
+ return null;
809
+ }
810
+ const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
811
+ if (broadened === void 0) {
812
+ return null;
813
+ }
814
+ return makeCustomConstraintNode(
815
+ broadened.extensionId,
816
+ broadened.registration.constraintName,
817
+ broadened.registration.parseValue(effectiveText),
818
+ provenance,
819
+ path2,
820
+ registry
821
+ );
822
+ }
823
+ function getBroadenedCustomTypeId(fieldType) {
824
+ if (fieldType?.kind === "custom") {
825
+ return fieldType.typeId;
826
+ }
827
+ if (fieldType?.kind !== "union") {
828
+ return void 0;
829
+ }
830
+ const customMembers = fieldType.members.filter(
831
+ (member) => member.kind === "custom"
832
+ );
833
+ if (customMembers.length !== 1) {
834
+ return void 0;
835
+ }
836
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
837
+ const allOtherMembersAreNull = nonCustomMembers.every(
838
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
839
+ );
840
+ const customMember = customMembers[0];
841
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
842
+ }
843
+ function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path2, registry) {
844
+ const constraintId = `${extensionId}/${constraintName}`;
845
+ const registration = registry.findConstraint(constraintId);
846
+ if (registration === void 0) {
847
+ throw new Error(
848
+ `Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
849
+ );
850
+ }
851
+ return {
852
+ kind: "constraint",
853
+ constraintKind: "custom",
854
+ constraintId,
855
+ payload,
856
+ compositionRule: registration.compositionRule,
857
+ ...path2 && { path: path2 },
858
+ provenance
859
+ };
860
+ }
814
861
  function parseDefaultValueValue(text, provenance) {
815
862
  const trimmed = text.trim();
816
863
  let value;
@@ -867,33 +914,33 @@ function getTagCommentText(tag) {
867
914
  if (typeof tag.comment === "string") {
868
915
  return tag.comment;
869
916
  }
870
- return ts2.getTextOfJSDocComment(tag.comment);
917
+ return ts.getTextOfJSDocComment(tag.comment);
871
918
  }
872
919
 
873
920
  // src/analyzer/jsdoc-constraints.ts
874
- function extractJSDocConstraintNodes(node, file = "") {
875
- const result = parseTSDocTags(node, file);
921
+ function extractJSDocConstraintNodes(node, file = "", options) {
922
+ const result = parseTSDocTags(node, file, options);
876
923
  return [...result.constraints];
877
924
  }
878
- function extractJSDocAnnotationNodes(node, file = "") {
879
- const result = parseTSDocTags(node, file);
925
+ function extractJSDocAnnotationNodes(node, file = "", options) {
926
+ const result = parseTSDocTags(node, file, options);
880
927
  return [...result.annotations];
881
928
  }
882
929
  function extractDefaultValueAnnotation(initializer, file = "") {
883
930
  if (!initializer) return null;
884
931
  let value;
885
- if (ts3.isStringLiteral(initializer)) {
932
+ if (ts2.isStringLiteral(initializer)) {
886
933
  value = initializer.text;
887
- } else if (ts3.isNumericLiteral(initializer)) {
934
+ } else if (ts2.isNumericLiteral(initializer)) {
888
935
  value = Number(initializer.text);
889
- } else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
936
+ } else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
890
937
  value = true;
891
- } else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
938
+ } else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
892
939
  value = false;
893
- } else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
940
+ } else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
894
941
  value = null;
895
- } else if (ts3.isPrefixUnaryExpression(initializer)) {
896
- if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
942
+ } else if (ts2.isPrefixUnaryExpression(initializer)) {
943
+ if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
897
944
  value = -Number(initializer.operand.text);
898
945
  }
899
946
  }
@@ -915,36 +962,56 @@ function extractDefaultValueAnnotation(initializer, file = "") {
915
962
 
916
963
  // src/analyzer/class-analyzer.ts
917
964
  function isObjectType(type) {
918
- return !!(type.flags & ts4.TypeFlags.Object);
965
+ return !!(type.flags & ts3.TypeFlags.Object);
919
966
  }
920
967
  function isTypeReference(type) {
921
- return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
968
+ return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
922
969
  }
923
970
  var RESOLVING_TYPE_PLACEHOLDER = {
924
971
  kind: "object",
925
972
  properties: [],
926
973
  additionalProperties: true
927
974
  };
928
- function analyzeClassToIR(classDecl, checker, file = "") {
975
+ function makeParseOptions(extensionRegistry, fieldType) {
976
+ if (extensionRegistry === void 0 && fieldType === void 0) {
977
+ return void 0;
978
+ }
979
+ return {
980
+ ...extensionRegistry !== void 0 && { extensionRegistry },
981
+ ...fieldType !== void 0 && { fieldType }
982
+ };
983
+ }
984
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
929
985
  const name = classDecl.name?.text ?? "AnonymousClass";
930
986
  const fields = [];
931
987
  const fieldLayouts = [];
932
988
  const typeRegistry = {};
933
- const annotations = extractJSDocAnnotationNodes(classDecl, file);
989
+ const annotations = extractJSDocAnnotationNodes(
990
+ classDecl,
991
+ file,
992
+ makeParseOptions(extensionRegistry)
993
+ );
934
994
  const visiting = /* @__PURE__ */ new Set();
935
995
  const instanceMethods = [];
936
996
  const staticMethods = [];
937
997
  for (const member of classDecl.members) {
938
- if (ts4.isPropertyDeclaration(member)) {
939
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
998
+ if (ts3.isPropertyDeclaration(member)) {
999
+ const fieldNode = analyzeFieldToIR(
1000
+ member,
1001
+ checker,
1002
+ file,
1003
+ typeRegistry,
1004
+ visiting,
1005
+ extensionRegistry
1006
+ );
940
1007
  if (fieldNode) {
941
1008
  fields.push(fieldNode);
942
1009
  fieldLayouts.push({});
943
1010
  }
944
- } else if (ts4.isMethodDeclaration(member)) {
1011
+ } else if (ts3.isMethodDeclaration(member)) {
945
1012
  const methodInfo = analyzeMethod(member, checker);
946
1013
  if (methodInfo) {
947
- const isStatic = member.modifiers?.some((m) => m.kind === ts4.SyntaxKind.StaticKeyword);
1014
+ const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
948
1015
  if (isStatic) {
949
1016
  staticMethods.push(methodInfo);
950
1017
  } else {
@@ -963,15 +1030,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
963
1030
  staticMethods
964
1031
  };
965
1032
  }
966
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1033
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
967
1034
  const name = interfaceDecl.name.text;
968
1035
  const fields = [];
969
1036
  const typeRegistry = {};
970
- const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1037
+ const annotations = extractJSDocAnnotationNodes(
1038
+ interfaceDecl,
1039
+ file,
1040
+ makeParseOptions(extensionRegistry)
1041
+ );
971
1042
  const visiting = /* @__PURE__ */ new Set();
972
1043
  for (const member of interfaceDecl.members) {
973
- if (ts4.isPropertySignature(member)) {
974
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1044
+ if (ts3.isPropertySignature(member)) {
1045
+ const fieldNode = analyzeInterfacePropertyToIR(
1046
+ member,
1047
+ checker,
1048
+ file,
1049
+ typeRegistry,
1050
+ visiting,
1051
+ extensionRegistry
1052
+ );
975
1053
  if (fieldNode) {
976
1054
  fields.push(fieldNode);
977
1055
  }
@@ -988,11 +1066,11 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
988
1066
  staticMethods: []
989
1067
  };
990
1068
  }
991
- function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
992
- if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1069
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1070
+ if (!ts3.isTypeLiteralNode(typeAlias.type)) {
993
1071
  const sourceFile = typeAlias.getSourceFile();
994
1072
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
995
- const kindDesc = ts4.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1073
+ const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
996
1074
  return {
997
1075
  ok: false,
998
1076
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
@@ -1001,11 +1079,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1001
1079
  const name = typeAlias.name.text;
1002
1080
  const fields = [];
1003
1081
  const typeRegistry = {};
1004
- const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1082
+ const annotations = extractJSDocAnnotationNodes(
1083
+ typeAlias,
1084
+ file,
1085
+ makeParseOptions(extensionRegistry)
1086
+ );
1005
1087
  const visiting = /* @__PURE__ */ new Set();
1006
1088
  for (const member of typeAlias.type.members) {
1007
- if (ts4.isPropertySignature(member)) {
1008
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1089
+ if (ts3.isPropertySignature(member)) {
1090
+ const fieldNode = analyzeInterfacePropertyToIR(
1091
+ member,
1092
+ checker,
1093
+ file,
1094
+ typeRegistry,
1095
+ visiting,
1096
+ extensionRegistry
1097
+ );
1009
1098
  if (fieldNode) {
1010
1099
  fields.push(fieldNode);
1011
1100
  }
@@ -1024,22 +1113,36 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1024
1113
  }
1025
1114
  };
1026
1115
  }
1027
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1028
- if (!ts4.isIdentifier(prop.name)) {
1116
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1117
+ if (!ts3.isIdentifier(prop.name)) {
1029
1118
  return null;
1030
1119
  }
1031
1120
  const name = prop.name.text;
1032
1121
  const tsType = checker.getTypeAtLocation(prop);
1033
1122
  const optional = prop.questionToken !== void 0;
1034
1123
  const provenance = provenanceForNode(prop, file);
1035
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1124
+ let type = resolveTypeNode(
1125
+ tsType,
1126
+ checker,
1127
+ file,
1128
+ typeRegistry,
1129
+ visiting,
1130
+ prop,
1131
+ extensionRegistry
1132
+ );
1036
1133
  const constraints = [];
1037
- if (prop.type) {
1038
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1134
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
1135
+ constraints.push(
1136
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
1137
+ );
1039
1138
  }
1040
- constraints.push(...extractJSDocConstraintNodes(prop, file));
1139
+ constraints.push(
1140
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
1141
+ );
1041
1142
  let annotations = [];
1042
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
1143
+ annotations.push(
1144
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
1145
+ );
1043
1146
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1044
1147
  if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1045
1148
  annotations.push(defaultAnnotation);
@@ -1055,22 +1158,36 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1055
1158
  provenance
1056
1159
  };
1057
1160
  }
1058
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
1059
- if (!ts4.isIdentifier(prop.name)) {
1161
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1162
+ if (!ts3.isIdentifier(prop.name)) {
1060
1163
  return null;
1061
1164
  }
1062
1165
  const name = prop.name.text;
1063
1166
  const tsType = checker.getTypeAtLocation(prop);
1064
1167
  const optional = prop.questionToken !== void 0;
1065
1168
  const provenance = provenanceForNode(prop, file);
1066
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1169
+ let type = resolveTypeNode(
1170
+ tsType,
1171
+ checker,
1172
+ file,
1173
+ typeRegistry,
1174
+ visiting,
1175
+ prop,
1176
+ extensionRegistry
1177
+ );
1067
1178
  const constraints = [];
1068
- if (prop.type) {
1069
- constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1179
+ if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
1180
+ constraints.push(
1181
+ ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
1182
+ );
1070
1183
  }
1071
- constraints.push(...extractJSDocConstraintNodes(prop, file));
1184
+ constraints.push(
1185
+ ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
1186
+ );
1072
1187
  let annotations = [];
1073
- annotations.push(...extractJSDocAnnotationNodes(prop, file));
1188
+ annotations.push(
1189
+ ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
1190
+ );
1074
1191
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1075
1192
  return {
1076
1193
  kind: "field",
@@ -1144,20 +1261,94 @@ function parseEnumMemberDisplayName(value) {
1144
1261
  if (label === "") return null;
1145
1262
  return { value: match[1], label };
1146
1263
  }
1147
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1148
- if (type.flags & ts4.TypeFlags.String) {
1264
+ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
1265
+ if (sourceNode === void 0 || extensionRegistry === void 0) {
1266
+ return null;
1267
+ }
1268
+ const typeNode = extractTypeNodeFromSource(sourceNode);
1269
+ if (typeNode === void 0) {
1270
+ return null;
1271
+ }
1272
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
1273
+ }
1274
+ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
1275
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
1276
+ return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
1277
+ }
1278
+ const typeName = getTypeNodeRegistrationName(typeNode);
1279
+ if (typeName === null) {
1280
+ return null;
1281
+ }
1282
+ const registration = extensionRegistry.findTypeByName(typeName);
1283
+ if (registration !== void 0) {
1284
+ return {
1285
+ kind: "custom",
1286
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
1287
+ payload: null
1288
+ };
1289
+ }
1290
+ if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
1291
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
1292
+ if (aliasDecl !== void 0) {
1293
+ return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
1294
+ }
1295
+ }
1296
+ return null;
1297
+ }
1298
+ function extractTypeNodeFromSource(sourceNode) {
1299
+ if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
1300
+ return sourceNode.type;
1301
+ }
1302
+ if (ts3.isTypeNode(sourceNode)) {
1303
+ return sourceNode;
1304
+ }
1305
+ return void 0;
1306
+ }
1307
+ function getTypeNodeRegistrationName(typeNode) {
1308
+ if (ts3.isTypeReferenceNode(typeNode)) {
1309
+ return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
1310
+ }
1311
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
1312
+ return getTypeNodeRegistrationName(typeNode.type);
1313
+ }
1314
+ if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
1315
+ return typeNode.getText();
1316
+ }
1317
+ return null;
1318
+ }
1319
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1320
+ const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
1321
+ if (customType) {
1322
+ return customType;
1323
+ }
1324
+ const primitiveAlias = tryResolveNamedPrimitiveAlias(
1325
+ type,
1326
+ checker,
1327
+ file,
1328
+ typeRegistry,
1329
+ visiting,
1330
+ sourceNode,
1331
+ extensionRegistry
1332
+ );
1333
+ if (primitiveAlias) {
1334
+ return primitiveAlias;
1335
+ }
1336
+ if (type.flags & ts3.TypeFlags.String) {
1149
1337
  return { kind: "primitive", primitiveKind: "string" };
1150
1338
  }
1151
- if (type.flags & ts4.TypeFlags.Number) {
1339
+ if (type.flags & ts3.TypeFlags.Number) {
1152
1340
  return { kind: "primitive", primitiveKind: "number" };
1153
1341
  }
1154
- if (type.flags & ts4.TypeFlags.Boolean) {
1342
+ if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
1343
+ return { kind: "primitive", primitiveKind: "bigint" };
1344
+ }
1345
+ if (type.flags & ts3.TypeFlags.Boolean) {
1155
1346
  return { kind: "primitive", primitiveKind: "boolean" };
1156
1347
  }
1157
- if (type.flags & ts4.TypeFlags.Null) {
1348
+ if (type.flags & ts3.TypeFlags.Null) {
1158
1349
  return { kind: "primitive", primitiveKind: "null" };
1159
1350
  }
1160
- if (type.flags & ts4.TypeFlags.Undefined) {
1351
+ if (type.flags & ts3.TypeFlags.Undefined) {
1161
1352
  return { kind: "primitive", primitiveKind: "null" };
1162
1353
  }
1163
1354
  if (type.isStringLiteral()) {
@@ -1173,27 +1364,120 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
1173
1364
  };
1174
1365
  }
1175
1366
  if (type.isUnion()) {
1176
- return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1367
+ return resolveUnionType(
1368
+ type,
1369
+ checker,
1370
+ file,
1371
+ typeRegistry,
1372
+ visiting,
1373
+ sourceNode,
1374
+ extensionRegistry
1375
+ );
1177
1376
  }
1178
1377
  if (checker.isArrayType(type)) {
1179
- return resolveArrayType(type, checker, file, typeRegistry, visiting);
1378
+ return resolveArrayType(
1379
+ type,
1380
+ checker,
1381
+ file,
1382
+ typeRegistry,
1383
+ visiting,
1384
+ sourceNode,
1385
+ extensionRegistry
1386
+ );
1180
1387
  }
1181
1388
  if (isObjectType(type)) {
1182
- return resolveObjectType(type, checker, file, typeRegistry, visiting);
1389
+ return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
1183
1390
  }
1184
1391
  return { kind: "primitive", primitiveKind: "string" };
1185
1392
  }
1186
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
1393
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1394
+ if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
1395
+ return null;
1396
+ }
1397
+ const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
1398
+ if (!aliasDecl) {
1399
+ return null;
1400
+ }
1401
+ const aliasName = aliasDecl.name.text;
1402
+ if (!typeRegistry[aliasName]) {
1403
+ const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
1404
+ const constraints = [
1405
+ ...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
1406
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
1407
+ ];
1408
+ const annotations = extractJSDocAnnotationNodes(
1409
+ aliasDecl,
1410
+ file,
1411
+ makeParseOptions(extensionRegistry)
1412
+ );
1413
+ typeRegistry[aliasName] = {
1414
+ name: aliasName,
1415
+ type: resolveAliasedPrimitiveTarget(
1416
+ aliasType,
1417
+ checker,
1418
+ file,
1419
+ typeRegistry,
1420
+ visiting,
1421
+ extensionRegistry
1422
+ ),
1423
+ ...constraints.length > 0 && { constraints },
1424
+ ...annotations.length > 0 && { annotations },
1425
+ provenance: provenanceForDeclaration(aliasDecl, file)
1426
+ };
1427
+ }
1428
+ return { kind: "reference", name: aliasName, typeArguments: [] };
1429
+ }
1430
+ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
1431
+ const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
1432
+ if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
1433
+ return void 0;
1434
+ }
1435
+ return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
1436
+ }
1437
+ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
1438
+ if (!ts3.isTypeReferenceNode(typeNode)) {
1439
+ return false;
1440
+ }
1441
+ const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
1442
+ if (!aliasDecl) {
1443
+ return false;
1444
+ }
1445
+ const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
1446
+ return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
1447
+ }
1448
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1449
+ const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
1450
+ if (nestedAliasDecl !== void 0) {
1451
+ return resolveAliasedPrimitiveTarget(
1452
+ checker.getTypeFromTypeNode(nestedAliasDecl.type),
1453
+ checker,
1454
+ file,
1455
+ typeRegistry,
1456
+ visiting,
1457
+ extensionRegistry
1458
+ );
1459
+ }
1460
+ return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
1461
+ }
1462
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1187
1463
  const typeName = getNamedTypeName(type);
1188
1464
  const namedDecl = getNamedTypeDeclaration(type);
1189
1465
  if (typeName && typeName in typeRegistry) {
1190
1466
  return { kind: "reference", name: typeName, typeArguments: [] };
1191
1467
  }
1192
1468
  const allTypes = type.types;
1469
+ const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
1470
+ const nonNullSourceNodes = unionMemberTypeNodes.filter(
1471
+ (memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
1472
+ );
1193
1473
  const nonNullTypes = allTypes.filter(
1194
- (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1474
+ (memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
1195
1475
  );
1196
- const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
1476
+ const nonNullMembers = nonNullTypes.map((memberType, index) => ({
1477
+ memberType,
1478
+ sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
1479
+ }));
1480
+ const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
1197
1481
  const memberDisplayNames = /* @__PURE__ */ new Map();
1198
1482
  if (namedDecl) {
1199
1483
  for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
@@ -1209,7 +1493,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1209
1493
  if (!typeName) {
1210
1494
  return result;
1211
1495
  }
1212
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
1496
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
1213
1497
  typeRegistry[typeName] = {
1214
1498
  name: typeName,
1215
1499
  type: result,
@@ -1222,7 +1506,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1222
1506
  const displayName = memberDisplayNames.get(String(value));
1223
1507
  return displayName !== void 0 ? { value, displayName } : { value };
1224
1508
  });
1225
- const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1509
+ const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
1226
1510
  if (isBooleanUnion2) {
1227
1511
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1228
1512
  const result = hasNull ? {
@@ -1257,14 +1541,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1257
1541
  } : enumNode;
1258
1542
  return registerNamed(result);
1259
1543
  }
1260
- if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1544
+ if (nonNullMembers.length === 1 && nonNullMembers[0]) {
1261
1545
  const inner = resolveTypeNode(
1262
- nonNullTypes[0],
1546
+ nonNullMembers[0].memberType,
1263
1547
  checker,
1264
1548
  file,
1265
1549
  typeRegistry,
1266
1550
  visiting,
1267
- sourceNode
1551
+ nonNullMembers[0].sourceNode ?? sourceNode,
1552
+ extensionRegistry
1268
1553
  );
1269
1554
  const result = hasNull ? {
1270
1555
  kind: "union",
@@ -1272,29 +1557,54 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1272
1557
  } : inner;
1273
1558
  return registerNamed(result);
1274
1559
  }
1275
- const members = nonNullTypes.map(
1276
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1560
+ const members = nonNullMembers.map(
1561
+ ({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
1562
+ memberType,
1563
+ checker,
1564
+ file,
1565
+ typeRegistry,
1566
+ visiting,
1567
+ memberSourceNode ?? sourceNode,
1568
+ extensionRegistry
1569
+ )
1277
1570
  );
1278
1571
  if (hasNull) {
1279
1572
  members.push({ kind: "primitive", primitiveKind: "null" });
1280
1573
  }
1281
1574
  return registerNamed({ kind: "union", members });
1282
1575
  }
1283
- function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1576
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1284
1577
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
1285
1578
  const elementType = typeArgs?.[0];
1286
- const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1579
+ const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
1580
+ const items = elementType ? resolveTypeNode(
1581
+ elementType,
1582
+ checker,
1583
+ file,
1584
+ typeRegistry,
1585
+ visiting,
1586
+ elementSourceNode,
1587
+ extensionRegistry
1588
+ ) : { kind: "primitive", primitiveKind: "string" };
1287
1589
  return { kind: "array", items };
1288
1590
  }
1289
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1591
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1290
1592
  if (type.getProperties().length > 0) {
1291
1593
  return null;
1292
1594
  }
1293
- const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
1595
+ const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
1294
1596
  if (!indexInfo) {
1295
1597
  return null;
1296
1598
  }
1297
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1599
+ const valueType = resolveTypeNode(
1600
+ indexInfo.type,
1601
+ checker,
1602
+ file,
1603
+ typeRegistry,
1604
+ visiting,
1605
+ void 0,
1606
+ extensionRegistry
1607
+ );
1298
1608
  return { kind: "record", valueType };
1299
1609
  }
1300
1610
  function typeNodeContainsReference(type, targetName) {
@@ -1322,7 +1632,7 @@ function typeNodeContainsReference(type, targetName) {
1322
1632
  }
1323
1633
  }
1324
1634
  }
1325
- function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1635
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1326
1636
  const typeName = getNamedTypeName(type);
1327
1637
  const namedTypeName = typeName ?? void 0;
1328
1638
  const namedDecl = getNamedTypeDeclaration(type);
@@ -1353,7 +1663,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1353
1663
  return { kind: "reference", name: namedTypeName, typeArguments: [] };
1354
1664
  }
1355
1665
  }
1356
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1666
+ const recordNode = tryResolveRecordType(
1667
+ type,
1668
+ checker,
1669
+ file,
1670
+ typeRegistry,
1671
+ visiting,
1672
+ extensionRegistry
1673
+ );
1357
1674
  if (recordNode) {
1358
1675
  visiting.delete(type);
1359
1676
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
@@ -1362,7 +1679,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1362
1679
  clearNamedTypeRegistration();
1363
1680
  return recordNode;
1364
1681
  }
1365
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
1682
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
1366
1683
  typeRegistry[namedTypeName] = {
1367
1684
  name: namedTypeName,
1368
1685
  type: recordNode,
@@ -1374,19 +1691,27 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1374
1691
  return recordNode;
1375
1692
  }
1376
1693
  const properties = [];
1377
- const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
1694
+ const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
1695
+ type,
1696
+ checker,
1697
+ file,
1698
+ typeRegistry,
1699
+ visiting,
1700
+ extensionRegistry
1701
+ );
1378
1702
  for (const prop of type.getProperties()) {
1379
1703
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1380
1704
  if (!declaration) continue;
1381
1705
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1382
- const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1706
+ const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
1383
1707
  const propTypeNode = resolveTypeNode(
1384
1708
  propType,
1385
1709
  checker,
1386
1710
  file,
1387
1711
  typeRegistry,
1388
1712
  visiting,
1389
- declaration
1713
+ declaration,
1714
+ extensionRegistry
1390
1715
  );
1391
1716
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1392
1717
  properties.push({
@@ -1405,7 +1730,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1405
1730
  additionalProperties: true
1406
1731
  };
1407
1732
  if (namedTypeName !== void 0 && shouldRegisterNamedType) {
1408
- const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
1733
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
1409
1734
  typeRegistry[namedTypeName] = {
1410
1735
  name: namedTypeName,
1411
1736
  type: objectNode,
@@ -1416,19 +1741,26 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1416
1741
  }
1417
1742
  return objectNode;
1418
1743
  }
1419
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
1744
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1420
1745
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
1421
1746
  (s) => s?.declarations != null && s.declarations.length > 0
1422
1747
  );
1423
1748
  for (const symbol of symbols) {
1424
1749
  const declarations = symbol.declarations;
1425
1750
  if (!declarations) continue;
1426
- const classDecl = declarations.find(ts4.isClassDeclaration);
1751
+ const classDecl = declarations.find(ts3.isClassDeclaration);
1427
1752
  if (classDecl) {
1428
1753
  const map = /* @__PURE__ */ new Map();
1429
1754
  for (const member of classDecl.members) {
1430
- if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
1431
- const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1755
+ if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
1756
+ const fieldNode = analyzeFieldToIR(
1757
+ member,
1758
+ checker,
1759
+ file,
1760
+ typeRegistry,
1761
+ visiting,
1762
+ extensionRegistry
1763
+ );
1432
1764
  if (fieldNode) {
1433
1765
  map.set(fieldNode.name, {
1434
1766
  constraints: [...fieldNode.constraints],
@@ -1440,28 +1772,86 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1440
1772
  }
1441
1773
  return map;
1442
1774
  }
1443
- const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
1775
+ const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
1444
1776
  if (interfaceDecl) {
1445
- return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
1777
+ return buildFieldNodeInfoMap(
1778
+ interfaceDecl.members,
1779
+ checker,
1780
+ file,
1781
+ typeRegistry,
1782
+ visiting,
1783
+ extensionRegistry
1784
+ );
1446
1785
  }
1447
- const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
1448
- if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
1786
+ const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
1787
+ if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
1449
1788
  return buildFieldNodeInfoMap(
1450
1789
  typeAliasDecl.type.members,
1451
1790
  checker,
1452
1791
  file,
1453
1792
  typeRegistry,
1454
- visiting
1793
+ visiting,
1794
+ extensionRegistry
1455
1795
  );
1456
1796
  }
1457
1797
  }
1458
1798
  return null;
1459
1799
  }
1460
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1800
+ function extractArrayElementTypeNode(sourceNode, checker) {
1801
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
1802
+ if (typeNode === void 0) {
1803
+ return void 0;
1804
+ }
1805
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
1806
+ if (ts3.isArrayTypeNode(resolvedTypeNode)) {
1807
+ return resolvedTypeNode.elementType;
1808
+ }
1809
+ if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
1810
+ return resolvedTypeNode.typeArguments[0];
1811
+ }
1812
+ return void 0;
1813
+ }
1814
+ function extractUnionMemberTypeNodes(sourceNode, checker) {
1815
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
1816
+ if (!typeNode) {
1817
+ return [];
1818
+ }
1819
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
1820
+ return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
1821
+ }
1822
+ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
1823
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
1824
+ return resolveAliasedTypeNode(typeNode.type, checker, visited);
1825
+ }
1826
+ if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
1827
+ return typeNode;
1828
+ }
1829
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1830
+ const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
1831
+ if (aliasDecl === void 0 || visited.has(aliasDecl)) {
1832
+ return typeNode;
1833
+ }
1834
+ visited.add(aliasDecl);
1835
+ return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
1836
+ }
1837
+ function isNullishTypeNode(typeNode) {
1838
+ if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
1839
+ return true;
1840
+ }
1841
+ return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
1842
+ }
1843
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
1461
1844
  const map = /* @__PURE__ */ new Map();
1462
1845
  for (const member of members) {
1463
- if (ts4.isPropertySignature(member)) {
1464
- const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1846
+ if (ts3.isPropertySignature(member)) {
1847
+ const fieldNode = analyzeInterfacePropertyToIR(
1848
+ member,
1849
+ checker,
1850
+ file,
1851
+ typeRegistry,
1852
+ visiting,
1853
+ extensionRegistry
1854
+ );
1465
1855
  if (fieldNode) {
1466
1856
  map.set(fieldNode.name, {
1467
1857
  constraints: [...fieldNode.constraints],
@@ -1474,8 +1864,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1474
1864
  return map;
1475
1865
  }
1476
1866
  var MAX_ALIAS_CHAIN_DEPTH = 8;
1477
- function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1478
- if (!ts4.isTypeReferenceNode(typeNode)) return [];
1867
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
1868
+ if (!ts3.isTypeReferenceNode(typeNode)) return [];
1479
1869
  if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
1480
1870
  const aliasName = typeNode.typeName.getText();
1481
1871
  throw new Error(
@@ -1484,11 +1874,26 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1484
1874
  }
1485
1875
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1486
1876
  if (!symbol?.declarations) return [];
1487
- const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
1877
+ const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
1488
1878
  if (!aliasDecl) return [];
1489
- if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
1490
- const constraints = extractJSDocConstraintNodes(aliasDecl, file);
1491
- constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
1879
+ if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
1880
+ const aliasFieldType = resolveTypeNode(
1881
+ checker.getTypeAtLocation(aliasDecl.type),
1882
+ checker,
1883
+ file,
1884
+ {},
1885
+ /* @__PURE__ */ new Set(),
1886
+ aliasDecl.type,
1887
+ extensionRegistry
1888
+ );
1889
+ const constraints = extractJSDocConstraintNodes(
1890
+ aliasDecl,
1891
+ file,
1892
+ makeParseOptions(extensionRegistry, aliasFieldType)
1893
+ );
1894
+ constraints.push(
1895
+ ...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
1896
+ );
1492
1897
  return constraints;
1493
1898
  }
1494
1899
  function provenanceForNode(node, file) {
@@ -1514,14 +1919,14 @@ function getNamedTypeName(type) {
1514
1919
  const symbol = type.getSymbol();
1515
1920
  if (symbol?.declarations) {
1516
1921
  const decl = symbol.declarations[0];
1517
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
1518
- const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
1922
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
1923
+ const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
1519
1924
  if (name) return name;
1520
1925
  }
1521
1926
  }
1522
1927
  const aliasSymbol = type.aliasSymbol;
1523
1928
  if (aliasSymbol?.declarations) {
1524
- const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
1929
+ const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
1525
1930
  if (aliasDecl) {
1526
1931
  return aliasDecl.name.text;
1527
1932
  }
@@ -1532,24 +1937,24 @@ function getNamedTypeDeclaration(type) {
1532
1937
  const symbol = type.getSymbol();
1533
1938
  if (symbol?.declarations) {
1534
1939
  const decl = symbol.declarations[0];
1535
- if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
1940
+ if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
1536
1941
  return decl;
1537
1942
  }
1538
1943
  }
1539
1944
  const aliasSymbol = type.aliasSymbol;
1540
1945
  if (aliasSymbol?.declarations) {
1541
- return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
1946
+ return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
1542
1947
  }
1543
1948
  return void 0;
1544
1949
  }
1545
1950
  function analyzeMethod(method, checker) {
1546
- if (!ts4.isIdentifier(method.name)) {
1951
+ if (!ts3.isIdentifier(method.name)) {
1547
1952
  return null;
1548
1953
  }
1549
1954
  const name = method.name.text;
1550
1955
  const parameters = [];
1551
1956
  for (const param of method.parameters) {
1552
- if (ts4.isIdentifier(param.name)) {
1957
+ if (ts3.isIdentifier(param.name)) {
1553
1958
  const paramInfo = analyzeParameter(param, checker);
1554
1959
  parameters.push(paramInfo);
1555
1960
  }
@@ -1560,7 +1965,7 @@ function analyzeMethod(method, checker) {
1560
1965
  return { name, parameters, returnTypeNode, returnType };
1561
1966
  }
1562
1967
  function analyzeParameter(param, checker) {
1563
- const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
1968
+ const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
1564
1969
  const typeNode = param.type;
1565
1970
  const type = checker.getTypeAtLocation(param);
1566
1971
  const formSpecExportName = detectFormSpecReference(typeNode);
@@ -1569,20 +1974,90 @@ function analyzeParameter(param, checker) {
1569
1974
  }
1570
1975
  function detectFormSpecReference(typeNode) {
1571
1976
  if (!typeNode) return null;
1572
- if (!ts4.isTypeReferenceNode(typeNode)) return null;
1573
- const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
1977
+ if (!ts3.isTypeReferenceNode(typeNode)) return null;
1978
+ const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
1574
1979
  if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
1575
1980
  const typeArg = typeNode.typeArguments?.[0];
1576
- if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
1577
- if (ts4.isIdentifier(typeArg.exprName)) {
1981
+ if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
1982
+ if (ts3.isIdentifier(typeArg.exprName)) {
1578
1983
  return typeArg.exprName.text;
1579
1984
  }
1580
- if (ts4.isQualifiedName(typeArg.exprName)) {
1985
+ if (ts3.isQualifiedName(typeArg.exprName)) {
1581
1986
  return typeArg.exprName.right.text;
1582
1987
  }
1583
1988
  return null;
1584
1989
  }
1585
1990
 
1991
+ // src/analyzer/program.ts
1992
+ function createProgramContext(filePath) {
1993
+ const absolutePath = path.resolve(filePath);
1994
+ const fileDir = path.dirname(absolutePath);
1995
+ const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
1996
+ let compilerOptions;
1997
+ let fileNames;
1998
+ if (configPath) {
1999
+ const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
2000
+ if (configFile.error) {
2001
+ throw new Error(
2002
+ `Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
2003
+ );
2004
+ }
2005
+ const parsed = ts4.parseJsonConfigFileContent(
2006
+ configFile.config,
2007
+ ts4.sys,
2008
+ path.dirname(configPath)
2009
+ );
2010
+ if (parsed.errors.length > 0) {
2011
+ const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
2012
+ throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
2013
+ }
2014
+ compilerOptions = parsed.options;
2015
+ fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
2016
+ } else {
2017
+ compilerOptions = {
2018
+ target: ts4.ScriptTarget.ES2022,
2019
+ module: ts4.ModuleKind.NodeNext,
2020
+ moduleResolution: ts4.ModuleResolutionKind.NodeNext,
2021
+ strict: true,
2022
+ skipLibCheck: true,
2023
+ declaration: true
2024
+ };
2025
+ fileNames = [absolutePath];
2026
+ }
2027
+ const program = ts4.createProgram(fileNames, compilerOptions);
2028
+ const sourceFile = program.getSourceFile(absolutePath);
2029
+ if (!sourceFile) {
2030
+ throw new Error(`Could not find source file: ${absolutePath}`);
2031
+ }
2032
+ return {
2033
+ program,
2034
+ checker: program.getTypeChecker(),
2035
+ sourceFile
2036
+ };
2037
+ }
2038
+ function findNodeByName(sourceFile, name, predicate, getName) {
2039
+ let result = null;
2040
+ function visit(node) {
2041
+ if (result) return;
2042
+ if (predicate(node) && getName(node) === name) {
2043
+ result = node;
2044
+ return;
2045
+ }
2046
+ ts4.forEachChild(node, visit);
2047
+ }
2048
+ visit(sourceFile);
2049
+ return result;
2050
+ }
2051
+ function findClassByName(sourceFile, className) {
2052
+ return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
2053
+ }
2054
+ function findInterfaceByName(sourceFile, interfaceName) {
2055
+ return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
2056
+ }
2057
+ function findTypeAliasByName(sourceFile, aliasName) {
2058
+ return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
2059
+ }
2060
+
1586
2061
  // src/json-schema/ir-generator.ts
1587
2062
  function makeContext(options) {
1588
2063
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -1601,6 +2076,9 @@ function generateJsonSchemaFromIR(ir, options) {
1601
2076
  const ctx = makeContext(options);
1602
2077
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
1603
2078
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
2079
+ if (typeDef.constraints && typeDef.constraints.length > 0) {
2080
+ applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
2081
+ }
1604
2082
  if (typeDef.annotations && typeDef.annotations.length > 0) {
1605
2083
  applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
1606
2084
  }
@@ -1769,7 +2247,9 @@ function generateTypeNode(type, ctx) {
1769
2247
  }
1770
2248
  }
1771
2249
  function generatePrimitiveType(type) {
1772
- return { type: type.primitiveKind };
2250
+ return {
2251
+ type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
2252
+ };
1773
2253
  }
1774
2254
  function generateEnumType(type) {
1775
2255
  const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
@@ -1942,7 +2422,7 @@ function applyAnnotations(schema, annotations, ctx) {
1942
2422
  case "deprecated":
1943
2423
  schema.deprecated = true;
1944
2424
  if (annotation.message !== void 0 && annotation.message !== "") {
1945
- schema["x-formspec-deprecation-description"] = annotation.message;
2425
+ schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
1946
2426
  }
1947
2427
  break;
1948
2428
  case "placeholder":
@@ -1975,7 +2455,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
1975
2455
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
1976
2456
  );
1977
2457
  }
1978
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
2458
+ assignVendorPrefixedExtensionKeywords(
2459
+ schema,
2460
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
2461
+ ctx.vendorPrefix,
2462
+ `custom constraint "${constraint.constraintId}"`
2463
+ );
1979
2464
  }
1980
2465
  function applyCustomAnnotation(schema, annotation, ctx) {
1981
2466
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -1987,7 +2472,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
1987
2472
  if (registration.toJsonSchema === void 0) {
1988
2473
  return;
1989
2474
  }
1990
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
2475
+ assignVendorPrefixedExtensionKeywords(
2476
+ schema,
2477
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
2478
+ ctx.vendorPrefix,
2479
+ `custom annotation "${annotation.annotationId}"`
2480
+ );
2481
+ }
2482
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
2483
+ for (const [key, value] of Object.entries(extensionSchema)) {
2484
+ if (!key.startsWith(`${vendorPrefix}-`)) {
2485
+ throw new Error(
2486
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
2487
+ );
2488
+ }
2489
+ schema[key] = value;
2490
+ }
1991
2491
  }
1992
2492
 
1993
2493
  // src/ui-schema/schema.ts
@@ -2213,16 +2713,8 @@ function generateUiSchemaFromIR(ir) {
2213
2713
  return parseOrThrow(uiSchema, result, "UI Schema");
2214
2714
  }
2215
2715
 
2216
- // src/generators/class-schema.ts
2217
- function generateClassSchemas(analysis, source) {
2218
- const ir = canonicalizeTSDoc(analysis, source);
2219
- return {
2220
- jsonSchema: generateJsonSchemaFromIR(ir),
2221
- uiSchema: generateUiSchemaFromIR(ir)
2222
- };
2223
- }
2224
-
2225
2716
  // src/validate/constraint-validator.ts
2717
+ import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
2226
2718
  function addContradiction(ctx, message, primary, related) {
2227
2719
  ctx.diagnostics.push({
2228
2720
  code: "CONTRADICTING_CONSTRAINTS",
@@ -2268,6 +2760,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
2268
2760
  relatedLocations: [related]
2269
2761
  });
2270
2762
  }
2763
+ function getExtensionIdFromConstraintId(constraintId) {
2764
+ const separator = constraintId.lastIndexOf("/");
2765
+ if (separator <= 0) {
2766
+ return null;
2767
+ }
2768
+ return constraintId.slice(0, separator);
2769
+ }
2271
2770
  function findNumeric(constraints, constraintKind) {
2272
2771
  return constraints.find((c) => c.constraintKind === constraintKind);
2273
2772
  }
@@ -2438,6 +2937,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
2438
2937
  strongestByKey.set(key, constraint);
2439
2938
  }
2440
2939
  }
2940
+ function compareCustomConstraintStrength(current, previous) {
2941
+ const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
2942
+ const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
2943
+ switch (current.role.bound) {
2944
+ case "lower":
2945
+ return equalPayloadTiebreaker;
2946
+ case "upper":
2947
+ return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
2948
+ case "exact":
2949
+ return order === 0 ? 0 : Number.NaN;
2950
+ default: {
2951
+ const _exhaustive = current.role.bound;
2952
+ return _exhaustive;
2953
+ }
2954
+ }
2955
+ }
2956
+ function compareSemanticInclusivity(currentInclusive, previousInclusive) {
2957
+ if (currentInclusive === previousInclusive) {
2958
+ return 0;
2959
+ }
2960
+ return currentInclusive ? -1 : 1;
2961
+ }
2962
+ function customConstraintsContradict(lower, upper) {
2963
+ const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
2964
+ if (order > 0) {
2965
+ return true;
2966
+ }
2967
+ if (order < 0) {
2968
+ return false;
2969
+ }
2970
+ return !lower.role.inclusive || !upper.role.inclusive;
2971
+ }
2972
+ function describeCustomConstraintTag(constraint) {
2973
+ return constraint.provenance.tagName ?? constraint.constraintId;
2974
+ }
2975
+ function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
2976
+ if (ctx.extensionRegistry === void 0) {
2977
+ return;
2978
+ }
2979
+ const strongestByKey = /* @__PURE__ */ new Map();
2980
+ const lowerByFamily = /* @__PURE__ */ new Map();
2981
+ const upperByFamily = /* @__PURE__ */ new Map();
2982
+ for (const constraint of constraints) {
2983
+ if (constraint.constraintKind !== "custom") {
2984
+ continue;
2985
+ }
2986
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
2987
+ if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
2988
+ continue;
2989
+ }
2990
+ const entry = {
2991
+ constraint,
2992
+ comparePayloads: registration.comparePayloads,
2993
+ role: registration.semanticRole
2994
+ };
2995
+ const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
2996
+ const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
2997
+ const previous = strongestByKey.get(boundKey);
2998
+ if (previous !== void 0) {
2999
+ const strength = compareCustomConstraintStrength(entry, previous);
3000
+ if (Number.isNaN(strength)) {
3001
+ addContradiction(
3002
+ ctx,
3003
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
3004
+ constraint.provenance,
3005
+ previous.constraint.provenance
3006
+ );
3007
+ continue;
3008
+ }
3009
+ if (strength < 0) {
3010
+ addConstraintBroadening(
3011
+ ctx,
3012
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
3013
+ constraint.provenance,
3014
+ previous.constraint.provenance
3015
+ );
3016
+ continue;
3017
+ }
3018
+ if (strength > 0) {
3019
+ strongestByKey.set(boundKey, entry);
3020
+ }
3021
+ } else {
3022
+ strongestByKey.set(boundKey, entry);
3023
+ }
3024
+ if (registration.semanticRole.bound === "lower") {
3025
+ lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3026
+ } else if (registration.semanticRole.bound === "upper") {
3027
+ upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
3028
+ }
3029
+ }
3030
+ for (const [familyKey, lower] of lowerByFamily) {
3031
+ const upper = upperByFamily.get(familyKey);
3032
+ if (upper === void 0) {
3033
+ continue;
3034
+ }
3035
+ if (!customConstraintsContradict(lower, upper)) {
3036
+ continue;
3037
+ }
3038
+ addContradiction(
3039
+ ctx,
3040
+ `Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
3041
+ lower.constraint.provenance,
3042
+ upper.constraint.provenance
3043
+ );
3044
+ }
3045
+ }
2441
3046
  function checkNumericContradictions(ctx, fieldName, constraints) {
2442
3047
  const min = findNumeric(constraints, "minimum");
2443
3048
  const max = findNumeric(constraints, "maximum");
@@ -2585,6 +3190,26 @@ function dereferenceType(ctx, type) {
2585
3190
  }
2586
3191
  return current;
2587
3192
  }
3193
+ function collectReferencedTypeConstraints(ctx, type) {
3194
+ const collected = [];
3195
+ let current = type;
3196
+ const seen = /* @__PURE__ */ new Set();
3197
+ while (current.kind === "reference") {
3198
+ if (seen.has(current.name)) {
3199
+ break;
3200
+ }
3201
+ seen.add(current.name);
3202
+ const definition = ctx.typeRegistry[current.name];
3203
+ if (definition === void 0) {
3204
+ break;
3205
+ }
3206
+ if (definition.constraints !== void 0) {
3207
+ collected.push(...definition.constraints);
3208
+ }
3209
+ current = definition.type;
3210
+ }
3211
+ return collected;
3212
+ }
2588
3213
  function resolvePathTargetType(ctx, type, segments) {
2589
3214
  const effectiveType = dereferenceType(ctx, type);
2590
3215
  if (segments.length === 0) {
@@ -2606,12 +3231,33 @@ function resolvePathTargetType(ctx, type, segments) {
2606
3231
  }
2607
3232
  return { kind: "unresolvable", type: effectiveType };
2608
3233
  }
3234
+ function isNullType(type) {
3235
+ return type.kind === "primitive" && type.primitiveKind === "null";
3236
+ }
3237
+ function collectCustomConstraintCandidateTypes(ctx, type) {
3238
+ const effectiveType = dereferenceType(ctx, type);
3239
+ const candidates = [effectiveType];
3240
+ if (effectiveType.kind === "array") {
3241
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
3242
+ }
3243
+ if (effectiveType.kind === "union") {
3244
+ const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
3245
+ const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
3246
+ if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
3247
+ const [nullableMember] = nonNullMembers;
3248
+ if (nullableMember !== void 0) {
3249
+ candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
3250
+ }
3251
+ }
3252
+ }
3253
+ return candidates;
3254
+ }
2609
3255
  function formatPathTargetFieldName(fieldName, path2) {
2610
3256
  return path2.length === 0 ? fieldName : `${fieldName}.${path2.join(".")}`;
2611
3257
  }
2612
3258
  function checkConstraintOnType(ctx, fieldName, type, constraint) {
2613
3259
  const effectiveType = dereferenceType(ctx, type);
2614
- const isNumber = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "number";
3260
+ const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
2615
3261
  const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
2616
3262
  const isArray = effectiveType.kind === "array";
2617
3263
  const isEnum = effectiveType.kind === "enum";
@@ -2669,7 +3315,9 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
2669
3315
  break;
2670
3316
  }
2671
3317
  case "const": {
2672
- const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
3318
+ const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
3319
+ effectiveType.primitiveKind
3320
+ ) || effectiveType.kind === "enum";
2673
3321
  if (!isPrimitiveConstType) {
2674
3322
  addTypeMismatch(
2675
3323
  ctx,
@@ -2680,7 +3328,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
2680
3328
  }
2681
3329
  if (effectiveType.kind === "primitive") {
2682
3330
  const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
2683
- if (valueType !== effectiveType.primitiveKind) {
3331
+ const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
3332
+ if (valueType !== expectedValueType) {
2684
3333
  addTypeMismatch(
2685
3334
  ctx,
2686
3335
  `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
@@ -2749,8 +3398,37 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
2749
3398
  );
2750
3399
  return;
2751
3400
  }
2752
- if (registration.applicableTypes === null) return;
2753
- if (!registration.applicableTypes.includes(type.kind)) {
3401
+ const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
3402
+ const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
3403
+ if (normalizedTagName !== void 0) {
3404
+ const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
3405
+ const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
3406
+ if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
3407
+ (candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
3408
+ )) {
3409
+ addTypeMismatch(
3410
+ ctx,
3411
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3412
+ constraint.provenance
3413
+ );
3414
+ return;
3415
+ }
3416
+ }
3417
+ if (registration.applicableTypes === null) {
3418
+ if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
3419
+ addTypeMismatch(
3420
+ ctx,
3421
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3422
+ constraint.provenance
3423
+ );
3424
+ }
3425
+ return;
3426
+ }
3427
+ const applicableTypes = registration.applicableTypes;
3428
+ const matchesApplicableType = candidateTypes.some(
3429
+ (candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
3430
+ );
3431
+ if (!matchesApplicableType) {
2754
3432
  addTypeMismatch(
2755
3433
  ctx,
2756
3434
  `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
@@ -2759,7 +3437,10 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
2759
3437
  }
2760
3438
  }
2761
3439
  function validateFieldNode(ctx, field) {
2762
- validateConstraints(ctx, field.name, field.type, field.constraints);
3440
+ validateConstraints(ctx, field.name, field.type, [
3441
+ ...collectReferencedTypeConstraints(ctx, field.type),
3442
+ ...field.constraints
3443
+ ]);
2763
3444
  if (field.type.kind === "object") {
2764
3445
  for (const prop of field.type.properties) {
2765
3446
  validateObjectProperty(ctx, field.name, prop);
@@ -2768,7 +3449,10 @@ function validateFieldNode(ctx, field) {
2768
3449
  }
2769
3450
  function validateObjectProperty(ctx, parentName, prop) {
2770
3451
  const qualifiedName = `${parentName}.${prop.name}`;
2771
- validateConstraints(ctx, qualifiedName, prop.type, prop.constraints);
3452
+ validateConstraints(ctx, qualifiedName, prop.type, [
3453
+ ...collectReferencedTypeConstraints(ctx, prop.type),
3454
+ ...prop.constraints
3455
+ ]);
2772
3456
  if (prop.type.kind === "object") {
2773
3457
  for (const nestedProp of prop.type.properties) {
2774
3458
  validateObjectProperty(ctx, qualifiedName, nestedProp);
@@ -2781,6 +3465,7 @@ function validateConstraints(ctx, name, type, constraints) {
2781
3465
  checkAllowedMembersContradiction(ctx, name, constraints);
2782
3466
  checkConstContradictions(ctx, name, constraints);
2783
3467
  checkConstraintBroadening(ctx, name, constraints);
3468
+ checkCustomConstraintSemantics(ctx, name, constraints);
2784
3469
  checkTypeApplicability(ctx, name, type, constraints);
2785
3470
  }
2786
3471
  function validateElement(ctx, element) {
@@ -2819,10 +3504,43 @@ function validateIR(ir, options) {
2819
3504
  };
2820
3505
  }
2821
3506
 
3507
+ // src/generators/class-schema.ts
3508
+ function generateClassSchemas(analysis, source, options) {
3509
+ const ir = canonicalizeTSDoc(analysis, source);
3510
+ const validationResult = validateIR(ir, {
3511
+ ...options?.extensionRegistry !== void 0 && {
3512
+ extensionRegistry: options.extensionRegistry
3513
+ },
3514
+ ...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
3515
+ });
3516
+ if (!validationResult.valid) {
3517
+ throw new Error(formatValidationError(validationResult.diagnostics));
3518
+ }
3519
+ return {
3520
+ jsonSchema: generateJsonSchemaFromIR(ir, options),
3521
+ uiSchema: generateUiSchemaFromIR(ir)
3522
+ };
3523
+ }
3524
+ function formatValidationError(diagnostics) {
3525
+ const lines = diagnostics.map((diagnostic) => {
3526
+ const primary = formatLocation(diagnostic.primaryLocation);
3527
+ const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
3528
+ return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
3529
+ });
3530
+ return `FormSpec validation failed:
3531
+ ${lines.map((line) => `- ${line}`).join("\n")}`;
3532
+ }
3533
+ function formatLocation(location) {
3534
+ return `${location.file}:${String(location.line)}:${String(location.column)}`;
3535
+ }
3536
+
2822
3537
  // src/extensions/registry.ts
2823
3538
  function createExtensionRegistry(extensions) {
2824
3539
  const typeMap = /* @__PURE__ */ new Map();
3540
+ const typeNameMap = /* @__PURE__ */ new Map();
2825
3541
  const constraintMap = /* @__PURE__ */ new Map();
3542
+ const constraintTagMap = /* @__PURE__ */ new Map();
3543
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
2826
3544
  const annotationMap = /* @__PURE__ */ new Map();
2827
3545
  for (const ext of extensions) {
2828
3546
  if (ext.types !== void 0) {
@@ -2832,6 +3550,27 @@ function createExtensionRegistry(extensions) {
2832
3550
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
2833
3551
  }
2834
3552
  typeMap.set(qualifiedId, type);
3553
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
3554
+ if (typeNameMap.has(sourceTypeName)) {
3555
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
3556
+ }
3557
+ typeNameMap.set(sourceTypeName, {
3558
+ extensionId: ext.extensionId,
3559
+ registration: type
3560
+ });
3561
+ }
3562
+ if (type.builtinConstraintBroadenings !== void 0) {
3563
+ for (const broadening of type.builtinConstraintBroadenings) {
3564
+ const key = `${qualifiedId}:${broadening.tagName}`;
3565
+ if (builtinBroadeningMap.has(key)) {
3566
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
3567
+ }
3568
+ builtinBroadeningMap.set(key, {
3569
+ extensionId: ext.extensionId,
3570
+ registration: broadening
3571
+ });
3572
+ }
3573
+ }
2835
3574
  }
2836
3575
  }
2837
3576
  if (ext.constraints !== void 0) {
@@ -2843,6 +3582,17 @@ function createExtensionRegistry(extensions) {
2843
3582
  constraintMap.set(qualifiedId, constraint);
2844
3583
  }
2845
3584
  }
3585
+ if (ext.constraintTags !== void 0) {
3586
+ for (const tag of ext.constraintTags) {
3587
+ if (constraintTagMap.has(tag.tagName)) {
3588
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
3589
+ }
3590
+ constraintTagMap.set(tag.tagName, {
3591
+ extensionId: ext.extensionId,
3592
+ registration: tag
3593
+ });
3594
+ }
3595
+ }
2846
3596
  if (ext.annotations !== void 0) {
2847
3597
  for (const annotation of ext.annotations) {
2848
3598
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -2856,7 +3606,10 @@ function createExtensionRegistry(extensions) {
2856
3606
  return {
2857
3607
  extensions,
2858
3608
  findType: (typeId) => typeMap.get(typeId),
3609
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
2859
3610
  findConstraint: (constraintId) => constraintMap.get(constraintId),
3611
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
3612
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
2860
3613
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
2861
3614
  };
2862
3615
  }
@@ -2882,6 +3635,7 @@ function typeToJsonSchema(type, checker) {
2882
3635
  provenance: fieldProvenance
2883
3636
  }
2884
3637
  ],
3638
+ rootAnnotations: [],
2885
3639
  typeRegistry,
2886
3640
  provenance: fieldProvenance
2887
3641
  };