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

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 (35) hide show
  1. package/dist/__tests__/fixtures/class-schema-regressions.d.ts +4 -0
  2. package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -1
  3. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  4. package/dist/analyzer/class-analyzer.d.ts +4 -1
  5. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  6. package/dist/analyzer/jsdoc-constraints.d.ts +2 -1
  7. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  8. package/dist/analyzer/tsdoc-parser.d.ts +13 -3
  9. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  10. package/dist/browser.cjs +30 -748
  11. package/dist/browser.cjs.map +1 -1
  12. package/dist/browser.js +32 -748
  13. package/dist/browser.js.map +1 -1
  14. package/dist/build.d.ts +14 -14
  15. package/dist/cli.cjs +691 -1101
  16. package/dist/cli.cjs.map +1 -1
  17. package/dist/cli.js +704 -1100
  18. package/dist/cli.js.map +1 -1
  19. package/dist/generators/class-schema.d.ts.map +1 -1
  20. package/dist/index.cjs +689 -1095
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.js +703 -1095
  23. package/dist/index.js.map +1 -1
  24. package/dist/internals.cjs +663 -1069
  25. package/dist/internals.cjs.map +1 -1
  26. package/dist/internals.js +675 -1067
  27. package/dist/internals.js.map +1 -1
  28. package/dist/ui-schema/schema.d.ts +14 -14
  29. package/dist/validate/constraint-validator.d.ts +6 -45
  30. package/dist/validate/constraint-validator.d.ts.map +1 -1
  31. package/package.json +2 -1
  32. package/dist/__tests__/json-utils.test.d.ts +0 -5
  33. package/dist/__tests__/json-utils.test.d.ts.map +0 -1
  34. package/dist/analyzer/json-utils.d.ts +0 -22
  35. package/dist/analyzer/json-utils.d.ts.map +0 -1
package/dist/internals.js CHANGED
@@ -398,6 +398,19 @@ import * as ts2 from "typescript";
398
398
 
399
399
  // src/analyzer/tsdoc-parser.ts
400
400
  import * as ts from "typescript";
401
+ import {
402
+ checkSyntheticTagApplication,
403
+ extractPathTarget as extractSharedPathTarget,
404
+ getTagDefinition,
405
+ hasTypeSemanticCapability,
406
+ parseConstraintTagValue,
407
+ parseDefaultValueTagValue,
408
+ resolveDeclarationPlacement,
409
+ resolvePathTargetType,
410
+ sliceCommentSpan,
411
+ parseCommentBlock,
412
+ parseTagSyntax
413
+ } from "@formspec/analysis";
401
414
  import {
402
415
  TSDocParser,
403
416
  TSDocConfiguration,
@@ -412,30 +425,6 @@ import {
412
425
  normalizeConstraintTagName,
413
426
  isBuiltinConstraintName
414
427
  } from "@formspec/core";
415
-
416
- // src/analyzer/json-utils.ts
417
- function tryParseJson(text) {
418
- try {
419
- return JSON.parse(text);
420
- } catch {
421
- return null;
422
- }
423
- }
424
-
425
- // src/analyzer/tsdoc-parser.ts
426
- var NUMERIC_CONSTRAINT_MAP = {
427
- minimum: "minimum",
428
- maximum: "maximum",
429
- exclusiveMinimum: "exclusiveMinimum",
430
- exclusiveMaximum: "exclusiveMaximum",
431
- multipleOf: "multipleOf"
432
- };
433
- var LENGTH_CONSTRAINT_MAP = {
434
- minLength: "minLength",
435
- maxLength: "maxLength",
436
- minItems: "minItems",
437
- maxItems: "maxItems"
438
- };
439
428
  var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
440
429
  function createFormSpecTSDocConfig(extensionTagNames = []) {
441
430
  const config = new TSDocConfiguration();
@@ -468,7 +457,294 @@ function createFormSpecTSDocConfig(extensionTagNames = []) {
468
457
  }
469
458
  return config;
470
459
  }
460
+ function sharedCommentSyntaxOptions(options, offset) {
461
+ const extensions = options?.extensionRegistry?.extensions;
462
+ return {
463
+ ...offset !== void 0 ? { offset } : {},
464
+ ...extensions !== void 0 ? { extensions } : {}
465
+ };
466
+ }
467
+ function sharedTagValueOptions(options) {
468
+ return {
469
+ ...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
470
+ ...options?.fieldType !== void 0 ? { fieldType: options.fieldType } : {}
471
+ };
472
+ }
473
+ var SYNTHETIC_TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
474
+ function buildSupportingDeclarations(sourceFile) {
475
+ return sourceFile.statements.filter(
476
+ (statement) => !ts.isImportDeclaration(statement) && !ts.isImportEqualsDeclaration(statement) && !(ts.isExportDeclaration(statement) && statement.moduleSpecifier !== void 0)
477
+ ).map((statement) => statement.getText(sourceFile));
478
+ }
479
+ function renderSyntheticArgumentExpression(valueKind, argumentText) {
480
+ const trimmed = argumentText.trim();
481
+ if (trimmed === "") {
482
+ return null;
483
+ }
484
+ switch (valueKind) {
485
+ case "number":
486
+ case "integer":
487
+ case "signedInteger":
488
+ return Number.isFinite(Number(trimmed)) ? trimmed : JSON.stringify(trimmed);
489
+ case "string":
490
+ return JSON.stringify(argumentText);
491
+ case "json":
492
+ try {
493
+ JSON.parse(trimmed);
494
+ return `(${trimmed})`;
495
+ } catch {
496
+ return JSON.stringify(trimmed);
497
+ }
498
+ case "boolean":
499
+ return trimmed === "true" || trimmed === "false" ? trimmed : JSON.stringify(trimmed);
500
+ case "condition":
501
+ return "undefined as unknown as FormSpecCondition";
502
+ case null:
503
+ return null;
504
+ default: {
505
+ return String(valueKind);
506
+ }
507
+ }
508
+ }
509
+ function getArrayElementType(type, checker) {
510
+ if (!checker.isArrayType(type)) {
511
+ return null;
512
+ }
513
+ return checker.getTypeArguments(type)[0] ?? null;
514
+ }
515
+ function supportsConstraintCapability(type, checker, capability) {
516
+ if (capability === void 0) {
517
+ return true;
518
+ }
519
+ if (hasTypeSemanticCapability(type, checker, capability)) {
520
+ return true;
521
+ }
522
+ if (capability === "string-like") {
523
+ const itemType = getArrayElementType(type, checker);
524
+ return itemType !== null && hasTypeSemanticCapability(itemType, checker, capability);
525
+ }
526
+ return false;
527
+ }
528
+ function makeDiagnostic(code, message, provenance) {
529
+ return {
530
+ code,
531
+ message,
532
+ severity: "error",
533
+ primaryLocation: provenance,
534
+ relatedLocations: []
535
+ };
536
+ }
537
+ function placementLabel(placement) {
538
+ switch (placement) {
539
+ case "class":
540
+ return "class declarations";
541
+ case "class-field":
542
+ return "class fields";
543
+ case "class-method":
544
+ return "class methods";
545
+ case "interface":
546
+ return "interface declarations";
547
+ case "interface-field":
548
+ return "interface fields";
549
+ case "type-alias":
550
+ return "type aliases";
551
+ case "type-alias-field":
552
+ return "type-alias properties";
553
+ case "variable":
554
+ return "variables";
555
+ case "function":
556
+ return "functions";
557
+ case "function-parameter":
558
+ return "function parameters";
559
+ case "method-parameter":
560
+ return "method parameters";
561
+ default: {
562
+ const exhaustive = placement;
563
+ return String(exhaustive);
564
+ }
565
+ }
566
+ }
567
+ function capabilityLabel(capability) {
568
+ switch (capability) {
569
+ case "numeric-comparable":
570
+ return "number";
571
+ case "string-like":
572
+ return "string";
573
+ case "array-like":
574
+ return "array";
575
+ case "enum-member-addressable":
576
+ return "enum";
577
+ case "json-like":
578
+ return "JSON-compatible";
579
+ case "object-like":
580
+ return "object";
581
+ case "condition-like":
582
+ return "conditional";
583
+ case void 0:
584
+ return "compatible";
585
+ default:
586
+ return capability;
587
+ }
588
+ }
589
+ function getBroadenedCustomTypeId(fieldType) {
590
+ if (fieldType?.kind === "custom") {
591
+ return fieldType.typeId;
592
+ }
593
+ if (fieldType?.kind !== "union") {
594
+ return void 0;
595
+ }
596
+ const customMembers = fieldType.members.filter(
597
+ (member) => member.kind === "custom"
598
+ );
599
+ if (customMembers.length !== 1) {
600
+ return void 0;
601
+ }
602
+ const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
603
+ const allOtherMembersAreNull = nonCustomMembers.every(
604
+ (member) => member.kind === "primitive" && member.primitiveKind === "null"
605
+ );
606
+ const customMember = customMembers[0];
607
+ return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
608
+ }
609
+ function hasBuiltinConstraintBroadening(tagName, options) {
610
+ const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
611
+ return broadenedTypeId !== void 0 && options?.extensionRegistry?.findBuiltinConstraintBroadening(broadenedTypeId, tagName) !== void 0;
612
+ }
613
+ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, parsedTag, provenance, supportingDeclarations, options) {
614
+ if (!isBuiltinConstraintName(tagName)) {
615
+ return [];
616
+ }
617
+ const checker = options?.checker;
618
+ const subjectType = options?.subjectType;
619
+ if (checker === void 0 || subjectType === void 0) {
620
+ return [];
621
+ }
622
+ const placement = resolveDeclarationPlacement(node);
623
+ if (placement === null) {
624
+ return [];
625
+ }
626
+ const definition = getTagDefinition(tagName, options?.extensionRegistry?.extensions);
627
+ if (definition === null) {
628
+ return [];
629
+ }
630
+ if (!definition.placements.includes(placement)) {
631
+ return [
632
+ makeDiagnostic(
633
+ "INVALID_TAG_PLACEMENT",
634
+ `Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
635
+ provenance
636
+ )
637
+ ];
638
+ }
639
+ const target = parsedTag?.target ?? null;
640
+ const hasBroadening = target === null && hasBuiltinConstraintBroadening(tagName, options);
641
+ if (target !== null) {
642
+ if (target.kind !== "path") {
643
+ return [
644
+ makeDiagnostic(
645
+ "UNSUPPORTED_TARGETING_SYNTAX",
646
+ `Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
647
+ provenance
648
+ )
649
+ ];
650
+ }
651
+ if (!target.valid || target.path === null) {
652
+ return [
653
+ makeDiagnostic(
654
+ "UNSUPPORTED_TARGETING_SYNTAX",
655
+ `Tag "@${tagName}" has invalid path targeting syntax.`,
656
+ provenance
657
+ )
658
+ ];
659
+ }
660
+ const resolution = resolvePathTargetType(subjectType, checker, target.path.segments);
661
+ if (resolution.kind === "missing-property") {
662
+ return [
663
+ makeDiagnostic(
664
+ "UNKNOWN_PATH_TARGET",
665
+ `Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
666
+ provenance
667
+ )
668
+ ];
669
+ }
670
+ if (resolution.kind === "unresolvable") {
671
+ const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
672
+ return [
673
+ makeDiagnostic(
674
+ "TYPE_MISMATCH",
675
+ `Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
676
+ provenance
677
+ )
678
+ ];
679
+ }
680
+ const requiredCapability = definition.capabilities[0];
681
+ if (requiredCapability !== void 0 && !supportsConstraintCapability(resolution.type, checker, requiredCapability)) {
682
+ const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
683
+ return [
684
+ makeDiagnostic(
685
+ "TYPE_MISMATCH",
686
+ `Target "${target.rawText}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
687
+ provenance
688
+ )
689
+ ];
690
+ }
691
+ } else if (!hasBroadening) {
692
+ const requiredCapability = definition.capabilities[0];
693
+ if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
694
+ const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
695
+ return [
696
+ makeDiagnostic(
697
+ "TYPE_MISMATCH",
698
+ `Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
699
+ provenance
700
+ )
701
+ ];
702
+ }
703
+ }
704
+ const argumentExpression = renderSyntheticArgumentExpression(
705
+ definition.valueKind,
706
+ parsedTag?.argumentText ?? ""
707
+ );
708
+ if (definition.requiresArgument && argumentExpression === null) {
709
+ return [];
710
+ }
711
+ if (hasBroadening) {
712
+ return [];
713
+ }
714
+ const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
715
+ const hostType = options?.hostType ?? subjectType;
716
+ const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
717
+ const result = checkSyntheticTagApplication({
718
+ tagName,
719
+ placement,
720
+ hostType: hostTypeText,
721
+ subjectType: subjectTypeText,
722
+ ...target?.kind === "path" ? { target: { kind: "path", text: target.rawText } } : {},
723
+ ...argumentExpression !== null ? { argumentExpression } : {},
724
+ supportingDeclarations,
725
+ ...options?.extensionRegistry !== void 0 ? {
726
+ extensions: options.extensionRegistry.extensions.map((extension) => ({
727
+ extensionId: extension.extensionId,
728
+ ...extension.constraintTags !== void 0 ? {
729
+ constraintTags: extension.constraintTags.map((tag) => ({ tagName: tag.tagName }))
730
+ } : {}
731
+ }))
732
+ } : {}
733
+ });
734
+ if (result.diagnostics.length === 0) {
735
+ return [];
736
+ }
737
+ const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
738
+ return [
739
+ makeDiagnostic(
740
+ "TYPE_MISMATCH",
741
+ `Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
742
+ provenance
743
+ )
744
+ ];
745
+ }
471
746
  var parserCache = /* @__PURE__ */ new Map();
747
+ var parseResultCache = /* @__PURE__ */ new Map();
472
748
  function getParser(options) {
473
749
  const extensionTagNames = [
474
750
  ...options?.extensionRegistry?.extensions.flatMap(
@@ -484,18 +760,54 @@ function getParser(options) {
484
760
  parserCache.set(cacheKey, parser);
485
761
  return parser;
486
762
  }
763
+ function getExtensionRegistryCacheKey(registry) {
764
+ if (registry === void 0) {
765
+ return "";
766
+ }
767
+ return registry.extensions.map(
768
+ (extension) => JSON.stringify({
769
+ extensionId: extension.extensionId,
770
+ typeNames: extension.types?.map((type) => type.typeName) ?? [],
771
+ constraintTags: extension.constraintTags?.map((tag) => tag.tagName) ?? []
772
+ })
773
+ ).join("|");
774
+ }
775
+ function getParseCacheKey(node, file, options) {
776
+ const sourceFile = node.getSourceFile();
777
+ const checker = options?.checker;
778
+ return JSON.stringify({
779
+ file,
780
+ sourceFile: sourceFile.fileName,
781
+ sourceText: sourceFile.text,
782
+ start: node.getFullStart(),
783
+ end: node.getEnd(),
784
+ fieldType: options?.fieldType ?? null,
785
+ subjectType: checker !== void 0 && options?.subjectType !== void 0 ? checker.typeToString(options.subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
786
+ hostType: checker !== void 0 && options?.hostType !== void 0 ? checker.typeToString(options.hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
787
+ extensions: getExtensionRegistryCacheKey(options?.extensionRegistry)
788
+ });
789
+ }
487
790
  function parseTSDocTags(node, file = "", options) {
791
+ const cacheKey = getParseCacheKey(node, file, options);
792
+ const cached = parseResultCache.get(cacheKey);
793
+ if (cached !== void 0) {
794
+ return cached;
795
+ }
488
796
  const constraints = [];
489
797
  const annotations = [];
798
+ const diagnostics = [];
490
799
  let displayName;
491
800
  let description;
492
801
  let placeholder;
493
802
  let displayNameProvenance;
494
803
  let descriptionProvenance;
495
804
  let placeholderProvenance;
805
+ const rawTextTags = [];
496
806
  const sourceFile = node.getSourceFile();
497
807
  const sourceText = sourceFile.getFullText();
808
+ const supportingDeclarations = buildSupportingDeclarations(sourceFile);
498
809
  const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
810
+ const rawTextFallbacks = collectRawTextFallbacks(node, file);
499
811
  if (commentRanges) {
500
812
  for (const range of commentRanges) {
501
813
  if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
@@ -510,12 +822,33 @@ function parseTSDocTags(node, file = "", options) {
510
822
  TextRange.fromStringRange(sourceText, range.pos, range.end)
511
823
  );
512
824
  const docComment = parserContext.docComment;
825
+ const parsedComment = parseCommentBlock(
826
+ commentText,
827
+ sharedCommentSyntaxOptions(options, range.pos)
828
+ );
829
+ let parsedTagCursor = 0;
830
+ const nextParsedTag = (normalizedTagName) => {
831
+ while (parsedTagCursor < parsedComment.tags.length) {
832
+ const candidate = parsedComment.tags[parsedTagCursor];
833
+ parsedTagCursor += 1;
834
+ if (candidate?.normalizedTagName === normalizedTagName) {
835
+ return candidate;
836
+ }
837
+ }
838
+ return null;
839
+ };
840
+ for (const parsedTag of parsedComment.tags) {
841
+ if (TAGS_REQUIRING_RAW_TEXT.has(parsedTag.normalizedTagName)) {
842
+ rawTextTags.push({ tag: parsedTag, commentText, commentOffset: range.pos });
843
+ }
844
+ }
513
845
  for (const block of docComment.customBlocks) {
514
846
  const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
847
+ const parsedTag = nextParsedTag(tagName);
515
848
  if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
516
- const text2 = extractBlockText(block).trim();
849
+ const text2 = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
517
850
  if (text2 === "") continue;
518
- const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
851
+ const provenance2 = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
519
852
  switch (tagName) {
520
853
  case "displayName":
521
854
  if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
@@ -545,11 +878,29 @@ function parseTSDocTags(node, file = "", options) {
545
878
  continue;
546
879
  }
547
880
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
548
- const text = extractBlockText(block).trim();
881
+ const text = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
549
882
  const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
550
883
  if (text === "" && expectedType !== "boolean") continue;
551
- const provenance = provenanceForComment(range, sourceFile, file, tagName);
552
- const constraintNode = parseConstraintValue(tagName, text, provenance, options);
884
+ const provenance = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
885
+ const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
886
+ node,
887
+ sourceFile,
888
+ tagName,
889
+ parsedTag,
890
+ provenance,
891
+ supportingDeclarations,
892
+ options
893
+ );
894
+ if (compilerDiagnostics.length > 0) {
895
+ diagnostics.push(...compilerDiagnostics);
896
+ continue;
897
+ }
898
+ const constraintNode = parseConstraintTagValue(
899
+ tagName,
900
+ text,
901
+ provenance,
902
+ sharedTagValueOptions(options)
903
+ );
553
904
  if (constraintNode) {
554
905
  constraints.push(constraintNode);
555
906
  }
@@ -603,57 +954,114 @@ function parseTSDocTags(node, file = "", options) {
603
954
  provenance: placeholderProvenance
604
955
  });
605
956
  }
606
- const jsDocTagsAll = ts.getJSDocTags(node);
607
- for (const tag of jsDocTagsAll) {
608
- const tagName = normalizeConstraintTagName(tag.tagName.text);
609
- if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
610
- const commentText = getTagCommentText(tag);
611
- if (commentText === void 0 || commentText.trim() === "") continue;
612
- const text = commentText.trim();
613
- const provenance = provenanceForJSDocTag(tag, file);
614
- if (tagName === "defaultValue") {
615
- const defaultValueNode = parseDefaultValueValue(text, provenance);
616
- annotations.push(defaultValueNode);
617
- continue;
957
+ if (rawTextTags.length > 0) {
958
+ for (const rawTextTag of rawTextTags) {
959
+ const fallbackQueue = rawTextFallbacks.get(rawTextTag.tag.normalizedTagName);
960
+ const fallback = fallbackQueue?.shift();
961
+ const text = choosePreferredPayloadText(
962
+ getSharedPayloadText(rawTextTag.tag, rawTextTag.commentText, rawTextTag.commentOffset),
963
+ fallback?.text ?? ""
964
+ );
965
+ if (text === "") continue;
966
+ const provenance = provenanceForParsedTag(rawTextTag.tag, sourceFile, file);
967
+ if (rawTextTag.tag.normalizedTagName === "defaultValue") {
968
+ const defaultValueNode = parseDefaultValueTagValue(text, provenance);
969
+ annotations.push(defaultValueNode);
970
+ continue;
971
+ }
972
+ const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
973
+ node,
974
+ sourceFile,
975
+ rawTextTag.tag.normalizedTagName,
976
+ rawTextTag.tag,
977
+ provenance,
978
+ supportingDeclarations,
979
+ options
980
+ );
981
+ if (compilerDiagnostics.length > 0) {
982
+ diagnostics.push(...compilerDiagnostics);
983
+ continue;
984
+ }
985
+ const constraintNode = parseConstraintTagValue(
986
+ rawTextTag.tag.normalizedTagName,
987
+ text,
988
+ provenance,
989
+ sharedTagValueOptions(options)
990
+ );
991
+ if (constraintNode) {
992
+ constraints.push(constraintNode);
993
+ }
618
994
  }
619
- const constraintNode = parseConstraintValue(tagName, text, provenance, options);
620
- if (constraintNode) {
621
- constraints.push(constraintNode);
995
+ }
996
+ for (const [tagName, fallbacks] of rawTextFallbacks) {
997
+ for (const fallback of fallbacks) {
998
+ const text = fallback.text.trim();
999
+ if (text === "") continue;
1000
+ const provenance = fallback.provenance;
1001
+ if (tagName === "defaultValue") {
1002
+ const defaultValueNode = parseDefaultValueTagValue(text, provenance);
1003
+ annotations.push(defaultValueNode);
1004
+ continue;
1005
+ }
1006
+ const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
1007
+ node,
1008
+ sourceFile,
1009
+ tagName,
1010
+ null,
1011
+ provenance,
1012
+ supportingDeclarations,
1013
+ options
1014
+ );
1015
+ if (compilerDiagnostics.length > 0) {
1016
+ diagnostics.push(...compilerDiagnostics);
1017
+ continue;
1018
+ }
1019
+ const constraintNode = parseConstraintTagValue(
1020
+ tagName,
1021
+ text,
1022
+ provenance,
1023
+ sharedTagValueOptions(options)
1024
+ );
1025
+ if (constraintNode) {
1026
+ constraints.push(constraintNode);
1027
+ }
622
1028
  }
623
1029
  }
624
- return { constraints, annotations };
1030
+ const result = { constraints, annotations, diagnostics };
1031
+ parseResultCache.set(cacheKey, result);
1032
+ return result;
625
1033
  }
626
1034
  function extractDisplayNameMetadata(node) {
627
1035
  let displayName;
628
1036
  const memberDisplayNames = /* @__PURE__ */ new Map();
629
- for (const tag of ts.getJSDocTags(node)) {
630
- const tagName = normalizeConstraintTagName(tag.tagName.text);
631
- if (tagName !== "displayName") continue;
632
- const commentText = getTagCommentText(tag);
633
- if (commentText === void 0) continue;
634
- const text = commentText.trim();
635
- if (text === "") continue;
636
- const memberTarget = parseMemberTargetDisplayName(text);
637
- if (memberTarget) {
638
- memberDisplayNames.set(memberTarget.target, memberTarget.label);
639
- continue;
640
- }
641
- displayName ??= text;
1037
+ const sourceFile = node.getSourceFile();
1038
+ const sourceText = sourceFile.getFullText();
1039
+ const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
1040
+ if (commentRanges) {
1041
+ for (const range of commentRanges) {
1042
+ if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) continue;
1043
+ const commentText = sourceText.substring(range.pos, range.end);
1044
+ if (!commentText.startsWith("/**")) continue;
1045
+ const parsed = parseCommentBlock(commentText);
1046
+ for (const tag of parsed.tags) {
1047
+ if (tag.normalizedTagName !== "displayName") {
1048
+ continue;
1049
+ }
1050
+ if (tag.target !== null && tag.argumentText !== "") {
1051
+ memberDisplayNames.set(tag.target.rawText, tag.argumentText);
1052
+ continue;
1053
+ }
1054
+ if (tag.argumentText !== "") {
1055
+ displayName ??= tag.argumentText;
1056
+ }
1057
+ }
1058
+ }
642
1059
  }
643
1060
  return {
644
1061
  ...displayName !== void 0 && { displayName },
645
1062
  memberDisplayNames
646
1063
  };
647
1064
  }
648
- function extractPathTarget(text) {
649
- const trimmed = text.trimStart();
650
- const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
651
- if (!match?.[1]) return null;
652
- return {
653
- path: { segments: [match[1]] },
654
- remainingText: match[2] ?? ""
655
- };
656
- }
657
1065
  function extractBlockText(block) {
658
1066
  return extractPlainText(block.content);
659
1067
  }
@@ -672,219 +1080,48 @@ function extractPlainText(node) {
672
1080
  }
673
1081
  return result;
674
1082
  }
675
- function parseConstraintValue(tagName, text, provenance, options) {
676
- const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
677
- if (customConstraint) {
678
- return customConstraint;
679
- }
680
- if (!isBuiltinConstraintName(tagName)) {
681
- return null;
682
- }
683
- const pathResult = extractPathTarget(text);
684
- const effectiveText = pathResult ? pathResult.remainingText : text;
685
- const path2 = pathResult?.path;
686
- const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
687
- if (expectedType === "number") {
688
- const value = Number(effectiveText);
689
- if (Number.isNaN(value)) {
690
- return null;
691
- }
692
- const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
693
- if (numericKind) {
694
- return {
695
- kind: "constraint",
696
- constraintKind: numericKind,
697
- value,
698
- ...path2 && { path: path2 },
699
- provenance
700
- };
701
- }
702
- const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
703
- if (lengthKind) {
704
- return {
705
- kind: "constraint",
706
- constraintKind: lengthKind,
707
- value,
708
- ...path2 && { path: path2 },
709
- provenance
710
- };
711
- }
712
- return null;
713
- }
714
- if (expectedType === "boolean") {
715
- const trimmed = effectiveText.trim();
716
- if (trimmed !== "" && trimmed !== "true") {
717
- return null;
718
- }
719
- if (tagName === "uniqueItems") {
720
- return {
721
- kind: "constraint",
722
- constraintKind: "uniqueItems",
723
- value: true,
724
- ...path2 && { path: path2 },
725
- provenance
726
- };
727
- }
728
- return null;
1083
+ function choosePreferredPayloadText(primary, fallback) {
1084
+ const preferred = primary.trim();
1085
+ const alternate = fallback.trim();
1086
+ if (preferred === "") return alternate;
1087
+ if (alternate === "") return preferred;
1088
+ if (alternate.includes("\n")) return alternate;
1089
+ if (alternate.length > preferred.length && alternate.startsWith(preferred)) {
1090
+ return alternate;
729
1091
  }
730
- if (expectedType === "json") {
731
- if (tagName === "const") {
732
- const trimmedText = effectiveText.trim();
733
- if (trimmedText === "") return null;
734
- try {
735
- const parsed2 = JSON.parse(trimmedText);
736
- return {
737
- kind: "constraint",
738
- constraintKind: "const",
739
- value: parsed2,
740
- ...path2 && { path: path2 },
741
- provenance
742
- };
743
- } catch {
744
- return {
745
- kind: "constraint",
746
- constraintKind: "const",
747
- value: trimmedText,
748
- ...path2 && { path: path2 },
749
- provenance
750
- };
751
- }
752
- }
753
- const parsed = tryParseJson(effectiveText);
754
- if (!Array.isArray(parsed)) {
755
- return null;
756
- }
757
- const members = [];
758
- for (const item of parsed) {
759
- if (typeof item === "string" || typeof item === "number") {
760
- members.push(item);
761
- } else if (typeof item === "object" && item !== null && "id" in item) {
762
- const id = item["id"];
763
- if (typeof id === "string" || typeof id === "number") {
764
- members.push(id);
765
- }
766
- }
767
- }
768
- return {
769
- kind: "constraint",
770
- constraintKind: "allowedMembers",
771
- members,
772
- ...path2 && { path: path2 },
773
- provenance
774
- };
775
- }
776
- return {
777
- kind: "constraint",
778
- constraintKind: "pattern",
779
- pattern: effectiveText,
780
- ...path2 && { path: path2 },
781
- provenance
782
- };
1092
+ return preferred;
783
1093
  }
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
- );
1094
+ function getSharedPayloadText(tag, commentText, commentOffset) {
1095
+ if (tag.payloadSpan === null) {
1096
+ return "";
802
1097
  }
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
- );
1098
+ return sliceCommentSpan(commentText, tag.payloadSpan, {
1099
+ offset: commentOffset
1100
+ }).trim();
822
1101
  }
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;
1102
+ function getBestBlockPayloadText(tag, commentText, commentOffset, block) {
1103
+ const sharedText = tag === null ? "" : getSharedPayloadText(tag, commentText, commentOffset);
1104
+ const blockText = extractBlockText(block).replace(/\s+/g, " ").trim();
1105
+ return choosePreferredPayloadText(sharedText, blockText);
842
1106
  }
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
- );
1107
+ function collectRawTextFallbacks(node, file) {
1108
+ const fallbacks = /* @__PURE__ */ new Map();
1109
+ for (const tag of ts.getJSDocTags(node)) {
1110
+ const tagName = normalizeConstraintTagName(tag.tagName.text);
1111
+ if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1112
+ const commentText = getTagCommentText(tag)?.trim() ?? "";
1113
+ if (commentText === "") continue;
1114
+ const entries = fallbacks.get(tagName) ?? [];
1115
+ entries.push({
1116
+ text: commentText,
1117
+ provenance: provenanceForJSDocTag(tag, file)
1118
+ });
1119
+ fallbacks.set(tagName, entries);
850
1120
  }
851
- return {
852
- kind: "constraint",
853
- constraintKind: "custom",
854
- constraintId,
855
- payload,
856
- compositionRule: registration.compositionRule,
857
- ...path2 && { path: path2 },
858
- provenance
859
- };
860
- }
861
- function parseDefaultValueValue(text, provenance) {
862
- const trimmed = text.trim();
863
- let value;
864
- if (trimmed === "null") {
865
- value = null;
866
- } else if (trimmed === "true") {
867
- value = true;
868
- } else if (trimmed === "false") {
869
- value = false;
870
- } else {
871
- const parsed = tryParseJson(trimmed);
872
- value = parsed !== null ? parsed : trimmed;
873
- }
874
- return {
875
- kind: "annotation",
876
- annotationKind: "defaultValue",
877
- value,
878
- provenance
879
- };
1121
+ return fallbacks;
880
1122
  }
881
1123
  function isMemberTargetDisplayName(text) {
882
- return parseMemberTargetDisplayName(text) !== null;
883
- }
884
- function parseMemberTargetDisplayName(text) {
885
- const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
886
- if (!match?.[1] || !match[2]) return null;
887
- return { target: match[1], label: match[2].trim() };
1124
+ return parseTagSyntax("displayName", text).target !== null;
888
1125
  }
889
1126
  function provenanceForComment(range, sourceFile, file, tagName) {
890
1127
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
@@ -896,6 +1133,16 @@ function provenanceForComment(range, sourceFile, file, tagName) {
896
1133
  tagName: "@" + tagName
897
1134
  };
898
1135
  }
1136
+ function provenanceForParsedTag(tag, sourceFile, file) {
1137
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.tagNameSpan.start);
1138
+ return {
1139
+ surface: "tsdoc",
1140
+ file,
1141
+ line: line + 1,
1142
+ column: character,
1143
+ tagName: "@" + tag.normalizedTagName
1144
+ };
1145
+ }
899
1146
  function provenanceForJSDocTag(tag, file) {
900
1147
  const sourceFile = tag.getSourceFile();
901
1148
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
@@ -918,12 +1165,15 @@ function getTagCommentText(tag) {
918
1165
  }
919
1166
 
920
1167
  // src/analyzer/jsdoc-constraints.ts
1168
+ function extractJSDocParseResult(node, file = "", options) {
1169
+ return parseTSDocTags(node, file, options);
1170
+ }
921
1171
  function extractJSDocConstraintNodes(node, file = "", options) {
922
- const result = parseTSDocTags(node, file, options);
1172
+ const result = extractJSDocParseResult(node, file, options);
923
1173
  return [...result.constraints];
924
1174
  }
925
1175
  function extractJSDocAnnotationNodes(node, file = "", options) {
926
- const result = parseTSDocTags(node, file, options);
1176
+ const result = extractJSDocParseResult(node, file, options);
927
1177
  return [...result.annotations];
928
1178
  }
929
1179
  function extractDefaultValueAnnotation(initializer, file = "") {
@@ -972,13 +1222,16 @@ var RESOLVING_TYPE_PLACEHOLDER = {
972
1222
  properties: [],
973
1223
  additionalProperties: true
974
1224
  };
975
- function makeParseOptions(extensionRegistry, fieldType) {
976
- if (extensionRegistry === void 0 && fieldType === void 0) {
1225
+ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, hostType) {
1226
+ if (extensionRegistry === void 0 && fieldType === void 0 && checker === void 0 && subjectType === void 0 && hostType === void 0) {
977
1227
  return void 0;
978
1228
  }
979
1229
  return {
980
1230
  ...extensionRegistry !== void 0 && { extensionRegistry },
981
- ...fieldType !== void 0 && { fieldType }
1231
+ ...fieldType !== void 0 && { fieldType },
1232
+ ...checker !== void 0 && { checker },
1233
+ ...subjectType !== void 0 && { subjectType },
1234
+ ...hostType !== void 0 && { hostType }
982
1235
  };
983
1236
  }
984
1237
  function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
@@ -986,11 +1239,15 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
986
1239
  const fields = [];
987
1240
  const fieldLayouts = [];
988
1241
  const typeRegistry = {};
989
- const annotations = extractJSDocAnnotationNodes(
1242
+ const diagnostics = [];
1243
+ const classType = checker.getTypeAtLocation(classDecl);
1244
+ const classDoc = extractJSDocParseResult(
990
1245
  classDecl,
991
1246
  file,
992
- makeParseOptions(extensionRegistry)
1247
+ makeParseOptions(extensionRegistry, void 0, checker, classType, classType)
993
1248
  );
1249
+ const annotations = [...classDoc.annotations];
1250
+ diagnostics.push(...classDoc.diagnostics);
994
1251
  const visiting = /* @__PURE__ */ new Set();
995
1252
  const instanceMethods = [];
996
1253
  const staticMethods = [];
@@ -1002,6 +1259,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1002
1259
  file,
1003
1260
  typeRegistry,
1004
1261
  visiting,
1262
+ diagnostics,
1263
+ classType,
1005
1264
  extensionRegistry
1006
1265
  );
1007
1266
  if (fieldNode) {
@@ -1026,6 +1285,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1026
1285
  fieldLayouts,
1027
1286
  typeRegistry,
1028
1287
  ...annotations.length > 0 && { annotations },
1288
+ ...diagnostics.length > 0 && { diagnostics },
1029
1289
  instanceMethods,
1030
1290
  staticMethods
1031
1291
  };
@@ -1034,11 +1294,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1034
1294
  const name = interfaceDecl.name.text;
1035
1295
  const fields = [];
1036
1296
  const typeRegistry = {};
1037
- const annotations = extractJSDocAnnotationNodes(
1297
+ const diagnostics = [];
1298
+ const interfaceType = checker.getTypeAtLocation(interfaceDecl);
1299
+ const interfaceDoc = extractJSDocParseResult(
1038
1300
  interfaceDecl,
1039
1301
  file,
1040
- makeParseOptions(extensionRegistry)
1302
+ makeParseOptions(extensionRegistry, void 0, checker, interfaceType, interfaceType)
1041
1303
  );
1304
+ const annotations = [...interfaceDoc.annotations];
1305
+ diagnostics.push(...interfaceDoc.diagnostics);
1042
1306
  const visiting = /* @__PURE__ */ new Set();
1043
1307
  for (const member of interfaceDecl.members) {
1044
1308
  if (ts3.isPropertySignature(member)) {
@@ -1048,6 +1312,8 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1048
1312
  file,
1049
1313
  typeRegistry,
1050
1314
  visiting,
1315
+ diagnostics,
1316
+ interfaceType,
1051
1317
  extensionRegistry
1052
1318
  );
1053
1319
  if (fieldNode) {
@@ -1062,6 +1328,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1062
1328
  fieldLayouts,
1063
1329
  typeRegistry,
1064
1330
  ...annotations.length > 0 && { annotations },
1331
+ ...diagnostics.length > 0 && { diagnostics },
1065
1332
  instanceMethods: [],
1066
1333
  staticMethods: []
1067
1334
  };
@@ -1079,11 +1346,15 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1079
1346
  const name = typeAlias.name.text;
1080
1347
  const fields = [];
1081
1348
  const typeRegistry = {};
1082
- const annotations = extractJSDocAnnotationNodes(
1349
+ const diagnostics = [];
1350
+ const aliasType = checker.getTypeAtLocation(typeAlias);
1351
+ const typeAliasDoc = extractJSDocParseResult(
1083
1352
  typeAlias,
1084
1353
  file,
1085
- makeParseOptions(extensionRegistry)
1354
+ makeParseOptions(extensionRegistry, void 0, checker, aliasType, aliasType)
1086
1355
  );
1356
+ const annotations = [...typeAliasDoc.annotations];
1357
+ diagnostics.push(...typeAliasDoc.diagnostics);
1087
1358
  const visiting = /* @__PURE__ */ new Set();
1088
1359
  for (const member of typeAlias.type.members) {
1089
1360
  if (ts3.isPropertySignature(member)) {
@@ -1093,6 +1364,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1093
1364
  file,
1094
1365
  typeRegistry,
1095
1366
  visiting,
1367
+ diagnostics,
1368
+ aliasType,
1096
1369
  extensionRegistry
1097
1370
  );
1098
1371
  if (fieldNode) {
@@ -1108,12 +1381,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1108
1381
  fieldLayouts: fields.map(() => ({})),
1109
1382
  typeRegistry,
1110
1383
  ...annotations.length > 0 && { annotations },
1384
+ ...diagnostics.length > 0 && { diagnostics },
1111
1385
  instanceMethods: [],
1112
1386
  staticMethods: []
1113
1387
  }
1114
1388
  };
1115
1389
  }
1116
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1390
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
1117
1391
  if (!ts3.isIdentifier(prop.name)) {
1118
1392
  return null;
1119
1393
  }
@@ -1128,7 +1402,8 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
1128
1402
  typeRegistry,
1129
1403
  visiting,
1130
1404
  prop,
1131
- extensionRegistry
1405
+ extensionRegistry,
1406
+ diagnostics
1132
1407
  );
1133
1408
  const constraints = [];
1134
1409
  if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
@@ -1136,13 +1411,15 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
1136
1411
  ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
1137
1412
  );
1138
1413
  }
1139
- constraints.push(
1140
- ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
1414
+ const docResult = extractJSDocParseResult(
1415
+ prop,
1416
+ file,
1417
+ makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
1141
1418
  );
1419
+ constraints.push(...docResult.constraints);
1420
+ diagnostics.push(...docResult.diagnostics);
1142
1421
  let annotations = [];
1143
- annotations.push(
1144
- ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
1145
- );
1422
+ annotations.push(...docResult.annotations);
1146
1423
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1147
1424
  if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1148
1425
  annotations.push(defaultAnnotation);
@@ -1158,7 +1435,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
1158
1435
  provenance
1159
1436
  };
1160
1437
  }
1161
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
1438
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
1162
1439
  if (!ts3.isIdentifier(prop.name)) {
1163
1440
  return null;
1164
1441
  }
@@ -1173,7 +1450,8 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1173
1450
  typeRegistry,
1174
1451
  visiting,
1175
1452
  prop,
1176
- extensionRegistry
1453
+ extensionRegistry,
1454
+ diagnostics
1177
1455
  );
1178
1456
  const constraints = [];
1179
1457
  if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
@@ -1181,13 +1459,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1181
1459
  ...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
1182
1460
  );
1183
1461
  }
1184
- constraints.push(
1185
- ...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
1462
+ const docResult = extractJSDocParseResult(
1463
+ prop,
1464
+ file,
1465
+ makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
1186
1466
  );
1467
+ constraints.push(...docResult.constraints);
1468
+ diagnostics.push(...docResult.diagnostics);
1187
1469
  let annotations = [];
1188
- annotations.push(
1189
- ...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
1190
- );
1470
+ annotations.push(...docResult.annotations);
1191
1471
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1192
1472
  return {
1193
1473
  kind: "field",
@@ -1316,7 +1596,7 @@ function getTypeNodeRegistrationName(typeNode) {
1316
1596
  }
1317
1597
  return null;
1318
1598
  }
1319
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1599
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
1320
1600
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
1321
1601
  if (customType) {
1322
1602
  return customType;
@@ -1328,7 +1608,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
1328
1608
  typeRegistry,
1329
1609
  visiting,
1330
1610
  sourceNode,
1331
- extensionRegistry
1611
+ extensionRegistry,
1612
+ diagnostics
1332
1613
  );
1333
1614
  if (primitiveAlias) {
1334
1615
  return primitiveAlias;
@@ -1371,7 +1652,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
1371
1652
  typeRegistry,
1372
1653
  visiting,
1373
1654
  sourceNode,
1374
- extensionRegistry
1655
+ extensionRegistry,
1656
+ diagnostics
1375
1657
  );
1376
1658
  }
1377
1659
  if (checker.isArrayType(type)) {
@@ -1382,15 +1664,24 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
1382
1664
  typeRegistry,
1383
1665
  visiting,
1384
1666
  sourceNode,
1385
- extensionRegistry
1667
+ extensionRegistry,
1668
+ diagnostics
1386
1669
  );
1387
1670
  }
1388
1671
  if (isObjectType(type)) {
1389
- return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
1672
+ return resolveObjectType(
1673
+ type,
1674
+ checker,
1675
+ file,
1676
+ typeRegistry,
1677
+ visiting,
1678
+ extensionRegistry,
1679
+ diagnostics
1680
+ );
1390
1681
  }
1391
1682
  return { kind: "primitive", primitiveKind: "string" };
1392
1683
  }
1393
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1684
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
1394
1685
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
1395
1686
  return null;
1396
1687
  }
@@ -1418,7 +1709,8 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
1418
1709
  file,
1419
1710
  typeRegistry,
1420
1711
  visiting,
1421
- extensionRegistry
1712
+ extensionRegistry,
1713
+ diagnostics
1422
1714
  ),
1423
1715
  ...constraints.length > 0 && { constraints },
1424
1716
  ...annotations.length > 0 && { annotations },
@@ -1445,7 +1737,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
1445
1737
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
1446
1738
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
1447
1739
  }
1448
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1740
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
1449
1741
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
1450
1742
  if (nestedAliasDecl !== void 0) {
1451
1743
  return resolveAliasedPrimitiveTarget(
@@ -1454,12 +1746,22 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
1454
1746
  file,
1455
1747
  typeRegistry,
1456
1748
  visiting,
1457
- extensionRegistry
1749
+ extensionRegistry,
1750
+ diagnostics
1458
1751
  );
1459
1752
  }
1460
- return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
1753
+ return resolveTypeNode(
1754
+ type,
1755
+ checker,
1756
+ file,
1757
+ typeRegistry,
1758
+ visiting,
1759
+ void 0,
1760
+ extensionRegistry,
1761
+ diagnostics
1762
+ );
1461
1763
  }
1462
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1764
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
1463
1765
  const typeName = getNamedTypeName(type);
1464
1766
  const namedDecl = getNamedTypeDeclaration(type);
1465
1767
  if (typeName && typeName in typeRegistry) {
@@ -1549,7 +1851,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1549
1851
  typeRegistry,
1550
1852
  visiting,
1551
1853
  nonNullMembers[0].sourceNode ?? sourceNode,
1552
- extensionRegistry
1854
+ extensionRegistry,
1855
+ diagnostics
1553
1856
  );
1554
1857
  const result = hasNull ? {
1555
1858
  kind: "union",
@@ -1565,7 +1868,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1565
1868
  typeRegistry,
1566
1869
  visiting,
1567
1870
  memberSourceNode ?? sourceNode,
1568
- extensionRegistry
1871
+ extensionRegistry,
1872
+ diagnostics
1569
1873
  )
1570
1874
  );
1571
1875
  if (hasNull) {
@@ -1573,7 +1877,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
1573
1877
  }
1574
1878
  return registerNamed({ kind: "union", members });
1575
1879
  }
1576
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
1880
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
1577
1881
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
1578
1882
  const elementType = typeArgs?.[0];
1579
1883
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -1584,11 +1888,12 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
1584
1888
  typeRegistry,
1585
1889
  visiting,
1586
1890
  elementSourceNode,
1587
- extensionRegistry
1891
+ extensionRegistry,
1892
+ diagnostics
1588
1893
  ) : { kind: "primitive", primitiveKind: "string" };
1589
1894
  return { kind: "array", items };
1590
1895
  }
1591
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1896
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
1592
1897
  if (type.getProperties().length > 0) {
1593
1898
  return null;
1594
1899
  }
@@ -1603,7 +1908,8 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
1603
1908
  typeRegistry,
1604
1909
  visiting,
1605
1910
  void 0,
1606
- extensionRegistry
1911
+ extensionRegistry,
1912
+ diagnostics
1607
1913
  );
1608
1914
  return { kind: "record", valueType };
1609
1915
  }
@@ -1632,7 +1938,7 @@ function typeNodeContainsReference(type, targetName) {
1632
1938
  }
1633
1939
  }
1634
1940
  }
1635
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
1941
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
1636
1942
  const typeName = getNamedTypeName(type);
1637
1943
  const namedTypeName = typeName ?? void 0;
1638
1944
  const namedDecl = getNamedTypeDeclaration(type);
@@ -1669,7 +1975,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
1669
1975
  file,
1670
1976
  typeRegistry,
1671
1977
  visiting,
1672
- extensionRegistry
1978
+ extensionRegistry,
1979
+ diagnostics
1673
1980
  );
1674
1981
  if (recordNode) {
1675
1982
  visiting.delete(type);
@@ -1697,6 +2004,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
1697
2004
  file,
1698
2005
  typeRegistry,
1699
2006
  visiting,
2007
+ diagnostics ?? [],
1700
2008
  extensionRegistry
1701
2009
  );
1702
2010
  for (const prop of type.getProperties()) {
@@ -1711,7 +2019,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
1711
2019
  typeRegistry,
1712
2020
  visiting,
1713
2021
  declaration,
1714
- extensionRegistry
2022
+ extensionRegistry,
2023
+ diagnostics
1715
2024
  );
1716
2025
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1717
2026
  properties.push({
@@ -1741,7 +2050,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
1741
2050
  }
1742
2051
  return objectNode;
1743
2052
  }
1744
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
2053
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
1745
2054
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
1746
2055
  (s) => s?.declarations != null && s.declarations.length > 0
1747
2056
  );
@@ -1751,6 +2060,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1751
2060
  const classDecl = declarations.find(ts3.isClassDeclaration);
1752
2061
  if (classDecl) {
1753
2062
  const map = /* @__PURE__ */ new Map();
2063
+ const hostType = checker.getTypeAtLocation(classDecl);
1754
2064
  for (const member of classDecl.members) {
1755
2065
  if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
1756
2066
  const fieldNode = analyzeFieldToIR(
@@ -1759,6 +2069,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1759
2069
  file,
1760
2070
  typeRegistry,
1761
2071
  visiting,
2072
+ diagnostics,
2073
+ hostType,
1762
2074
  extensionRegistry
1763
2075
  );
1764
2076
  if (fieldNode) {
@@ -1780,6 +2092,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1780
2092
  file,
1781
2093
  typeRegistry,
1782
2094
  visiting,
2095
+ checker.getTypeAtLocation(interfaceDecl),
2096
+ diagnostics,
1783
2097
  extensionRegistry
1784
2098
  );
1785
2099
  }
@@ -1791,6 +2105,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
1791
2105
  file,
1792
2106
  typeRegistry,
1793
2107
  visiting,
2108
+ checker.getTypeAtLocation(typeAliasDecl),
2109
+ diagnostics,
1794
2110
  extensionRegistry
1795
2111
  );
1796
2112
  }
@@ -1840,7 +2156,7 @@ function isNullishTypeNode(typeNode) {
1840
2156
  }
1841
2157
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
1842
2158
  }
1843
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
2159
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
1844
2160
  const map = /* @__PURE__ */ new Map();
1845
2161
  for (const member of members) {
1846
2162
  if (ts3.isPropertySignature(member)) {
@@ -1850,6 +2166,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
1850
2166
  file,
1851
2167
  typeRegistry,
1852
2168
  visiting,
2169
+ diagnostics,
2170
+ hostType,
1853
2171
  extensionRegistry
1854
2172
  );
1855
2173
  if (fieldNode) {
@@ -2714,760 +3032,44 @@ function generateUiSchemaFromIR(ir) {
2714
3032
  }
2715
3033
 
2716
3034
  // src/validate/constraint-validator.ts
2717
- import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
2718
- function addContradiction(ctx, message, primary, related) {
2719
- ctx.diagnostics.push({
2720
- code: "CONTRADICTING_CONSTRAINTS",
2721
- message,
2722
- severity: "error",
2723
- primaryLocation: primary,
2724
- relatedLocations: [related]
2725
- });
2726
- }
2727
- function addTypeMismatch(ctx, message, primary) {
2728
- ctx.diagnostics.push({
2729
- code: "TYPE_MISMATCH",
2730
- message,
2731
- severity: "error",
2732
- primaryLocation: primary,
2733
- relatedLocations: []
2734
- });
2735
- }
2736
- function addUnknownExtension(ctx, message, primary) {
2737
- ctx.diagnostics.push({
2738
- code: "UNKNOWN_EXTENSION",
2739
- message,
2740
- severity: "warning",
2741
- primaryLocation: primary,
2742
- relatedLocations: []
2743
- });
2744
- }
2745
- function addUnknownPathTarget(ctx, message, primary) {
2746
- ctx.diagnostics.push({
2747
- code: "UNKNOWN_PATH_TARGET",
2748
- message,
2749
- severity: "error",
2750
- primaryLocation: primary,
2751
- relatedLocations: []
2752
- });
2753
- }
2754
- function addConstraintBroadening(ctx, message, primary, related) {
2755
- ctx.diagnostics.push({
2756
- code: "CONSTRAINT_BROADENING",
2757
- message,
2758
- severity: "error",
2759
- primaryLocation: primary,
2760
- relatedLocations: [related]
2761
- });
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
- }
2770
- function findNumeric(constraints, constraintKind) {
2771
- return constraints.find((c) => c.constraintKind === constraintKind);
2772
- }
2773
- function findLength(constraints, constraintKind) {
2774
- return constraints.find((c) => c.constraintKind === constraintKind);
2775
- }
2776
- function findAllowedMembers(constraints) {
2777
- return constraints.filter(
2778
- (c) => c.constraintKind === "allowedMembers"
2779
- );
2780
- }
2781
- function findConstConstraints(constraints) {
2782
- return constraints.filter(
2783
- (c) => c.constraintKind === "const"
2784
- );
2785
- }
2786
- function jsonValueEquals(left, right) {
2787
- if (left === right) {
2788
- return true;
2789
- }
2790
- if (Array.isArray(left) || Array.isArray(right)) {
2791
- if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
2792
- return false;
2793
- }
2794
- return left.every((item, index) => jsonValueEquals(item, right[index]));
2795
- }
2796
- if (isJsonObject(left) || isJsonObject(right)) {
2797
- if (!isJsonObject(left) || !isJsonObject(right)) {
2798
- return false;
2799
- }
2800
- const leftKeys = Object.keys(left).sort();
2801
- const rightKeys = Object.keys(right).sort();
2802
- if (leftKeys.length !== rightKeys.length) {
2803
- return false;
2804
- }
2805
- return leftKeys.every((key, index) => {
2806
- const rightKey = rightKeys[index];
2807
- if (rightKey !== key) {
2808
- return false;
2809
- }
2810
- const leftValue = left[key];
2811
- const rightValue = right[rightKey];
2812
- return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
2813
- });
2814
- }
2815
- return false;
2816
- }
2817
- function isJsonObject(value) {
2818
- return typeof value === "object" && value !== null && !Array.isArray(value);
2819
- }
2820
- function isOrderedBoundConstraint(constraint) {
2821
- return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
2822
- }
2823
- function pathKey(constraint) {
2824
- return constraint.path?.segments.join(".") ?? "";
2825
- }
2826
- function orderedBoundFamily(kind) {
2827
- switch (kind) {
2828
- case "minimum":
2829
- case "exclusiveMinimum":
2830
- return "numeric-lower";
2831
- case "maximum":
2832
- case "exclusiveMaximum":
2833
- return "numeric-upper";
2834
- case "minLength":
2835
- return "minLength";
2836
- case "minItems":
2837
- return "minItems";
2838
- case "maxLength":
2839
- return "maxLength";
2840
- case "maxItems":
2841
- return "maxItems";
2842
- default: {
2843
- const _exhaustive = kind;
2844
- return _exhaustive;
2845
- }
2846
- }
2847
- }
2848
- function isNumericLowerKind(kind) {
2849
- return kind === "minimum" || kind === "exclusiveMinimum";
2850
- }
2851
- function isNumericUpperKind(kind) {
2852
- return kind === "maximum" || kind === "exclusiveMaximum";
2853
- }
2854
- function describeConstraintTag(constraint) {
2855
- return `@${constraint.constraintKind}`;
2856
- }
2857
- function compareConstraintStrength(current, previous) {
2858
- const family = orderedBoundFamily(current.constraintKind);
2859
- if (family === "numeric-lower") {
2860
- if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
2861
- throw new Error("numeric-lower family received non-numeric lower-bound constraint");
2862
- }
2863
- if (current.value !== previous.value) {
2864
- return current.value > previous.value ? 1 : -1;
2865
- }
2866
- if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
2867
- return 1;
2868
- }
2869
- if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
2870
- return -1;
2871
- }
2872
- return 0;
2873
- }
2874
- if (family === "numeric-upper") {
2875
- if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
2876
- throw new Error("numeric-upper family received non-numeric upper-bound constraint");
2877
- }
2878
- if (current.value !== previous.value) {
2879
- return current.value < previous.value ? 1 : -1;
2880
- }
2881
- if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
2882
- return 1;
2883
- }
2884
- if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
2885
- return -1;
2886
- }
2887
- return 0;
2888
- }
2889
- switch (family) {
2890
- case "minLength":
2891
- case "minItems":
2892
- if (current.value === previous.value) {
2893
- return 0;
2894
- }
2895
- return current.value > previous.value ? 1 : -1;
2896
- case "maxLength":
2897
- case "maxItems":
2898
- if (current.value === previous.value) {
2899
- return 0;
2900
- }
2901
- return current.value < previous.value ? 1 : -1;
2902
- default: {
2903
- const _exhaustive = family;
2904
- return _exhaustive;
2905
- }
2906
- }
2907
- }
2908
- function checkConstraintBroadening(ctx, fieldName, constraints) {
2909
- const strongestByKey = /* @__PURE__ */ new Map();
2910
- for (const constraint of constraints) {
2911
- if (!isOrderedBoundConstraint(constraint)) {
2912
- continue;
2913
- }
2914
- const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
2915
- const previous = strongestByKey.get(key);
2916
- if (previous === void 0) {
2917
- strongestByKey.set(key, constraint);
2918
- continue;
2919
- }
2920
- const strength = compareConstraintStrength(constraint, previous);
2921
- if (strength < 0) {
2922
- const displayFieldName = formatPathTargetFieldName(
2923
- fieldName,
2924
- constraint.path?.segments ?? []
2925
- );
2926
- addConstraintBroadening(
2927
- ctx,
2928
- `Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
2929
- constraint.provenance,
2930
- previous.provenance
2931
- );
2932
- continue;
2933
- }
2934
- if (strength <= 0) {
2935
- continue;
2936
- }
2937
- strongestByKey.set(key, constraint);
2938
- }
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
- }
3046
- function checkNumericContradictions(ctx, fieldName, constraints) {
3047
- const min = findNumeric(constraints, "minimum");
3048
- const max = findNumeric(constraints, "maximum");
3049
- const exMin = findNumeric(constraints, "exclusiveMinimum");
3050
- const exMax = findNumeric(constraints, "exclusiveMaximum");
3051
- if (min !== void 0 && max !== void 0 && min.value > max.value) {
3052
- addContradiction(
3053
- ctx,
3054
- `Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
3055
- min.provenance,
3056
- max.provenance
3057
- );
3058
- }
3059
- if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
3060
- addContradiction(
3061
- ctx,
3062
- `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
3063
- exMin.provenance,
3064
- max.provenance
3065
- );
3066
- }
3067
- if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
3068
- addContradiction(
3069
- ctx,
3070
- `Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3071
- min.provenance,
3072
- exMax.provenance
3073
- );
3074
- }
3075
- if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
3076
- addContradiction(
3077
- ctx,
3078
- `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
3079
- exMin.provenance,
3080
- exMax.provenance
3081
- );
3082
- }
3083
- }
3084
- function checkLengthContradictions(ctx, fieldName, constraints) {
3085
- const minLen = findLength(constraints, "minLength");
3086
- const maxLen = findLength(constraints, "maxLength");
3087
- if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
3088
- addContradiction(
3089
- ctx,
3090
- `Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
3091
- minLen.provenance,
3092
- maxLen.provenance
3093
- );
3094
- }
3095
- const minItems = findLength(constraints, "minItems");
3096
- const maxItems = findLength(constraints, "maxItems");
3097
- if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
3098
- addContradiction(
3099
- ctx,
3100
- `Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
3101
- minItems.provenance,
3102
- maxItems.provenance
3103
- );
3104
- }
3105
- }
3106
- function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
3107
- const members = findAllowedMembers(constraints);
3108
- if (members.length < 2) return;
3109
- const firstSet = new Set(members[0]?.members ?? []);
3110
- for (let i = 1; i < members.length; i++) {
3111
- const current = members[i];
3112
- if (current === void 0) continue;
3113
- for (const m of firstSet) {
3114
- if (!current.members.includes(m)) {
3115
- firstSet.delete(m);
3116
- }
3117
- }
3118
- }
3119
- if (firstSet.size === 0) {
3120
- const first = members[0];
3121
- const second = members[1];
3122
- if (first !== void 0 && second !== void 0) {
3123
- addContradiction(
3124
- ctx,
3125
- `Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
3126
- first.provenance,
3127
- second.provenance
3128
- );
3129
- }
3130
- }
3131
- }
3132
- function checkConstContradictions(ctx, fieldName, constraints) {
3133
- const constConstraints = findConstConstraints(constraints);
3134
- if (constConstraints.length < 2) return;
3135
- const first = constConstraints[0];
3136
- if (first === void 0) return;
3137
- for (let i = 1; i < constConstraints.length; i++) {
3138
- const current = constConstraints[i];
3139
- if (current === void 0) continue;
3140
- if (jsonValueEquals(first.value, current.value)) {
3141
- continue;
3142
- }
3143
- addContradiction(
3144
- ctx,
3145
- `Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
3146
- first.provenance,
3147
- current.provenance
3148
- );
3149
- }
3150
- }
3151
- function typeLabel(type) {
3152
- switch (type.kind) {
3153
- case "primitive":
3154
- return type.primitiveKind;
3155
- case "enum":
3156
- return "enum";
3157
- case "array":
3158
- return "array";
3159
- case "object":
3160
- return "object";
3161
- case "record":
3162
- return "record";
3163
- case "union":
3164
- return "union";
3165
- case "reference":
3166
- return `reference(${type.name})`;
3167
- case "dynamic":
3168
- return `dynamic(${type.dynamicKind})`;
3169
- case "custom":
3170
- return `custom(${type.typeId})`;
3171
- default: {
3172
- const _exhaustive = type;
3173
- return String(_exhaustive);
3174
- }
3175
- }
3176
- }
3177
- function dereferenceType(ctx, type) {
3178
- let current = type;
3179
- const seen = /* @__PURE__ */ new Set();
3180
- while (current.kind === "reference") {
3181
- if (seen.has(current.name)) {
3182
- return current;
3183
- }
3184
- seen.add(current.name);
3185
- const definition = ctx.typeRegistry[current.name];
3186
- if (definition === void 0) {
3187
- return current;
3188
- }
3189
- current = definition.type;
3190
- }
3191
- return current;
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
- }
3213
- function resolvePathTargetType(ctx, type, segments) {
3214
- const effectiveType = dereferenceType(ctx, type);
3215
- if (segments.length === 0) {
3216
- return { kind: "resolved", type: effectiveType };
3217
- }
3218
- if (effectiveType.kind === "array") {
3219
- return resolvePathTargetType(ctx, effectiveType.items, segments);
3220
- }
3221
- if (effectiveType.kind === "object") {
3222
- const [segment, ...rest] = segments;
3223
- if (segment === void 0) {
3224
- throw new Error("Invariant violation: object path traversal requires a segment");
3225
- }
3226
- const property = effectiveType.properties.find((prop) => prop.name === segment);
3227
- if (property === void 0) {
3228
- return { kind: "missing-property", segment };
3229
- }
3230
- return resolvePathTargetType(ctx, property.type, rest);
3231
- }
3232
- return { kind: "unresolvable", type: effectiveType };
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
- }
3255
- function formatPathTargetFieldName(fieldName, path2) {
3256
- return path2.length === 0 ? fieldName : `${fieldName}.${path2.join(".")}`;
3257
- }
3258
- function checkConstraintOnType(ctx, fieldName, type, constraint) {
3259
- const effectiveType = dereferenceType(ctx, type);
3260
- const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
3261
- const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
3262
- const isArray = effectiveType.kind === "array";
3263
- const isEnum = effectiveType.kind === "enum";
3264
- const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
3265
- const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
3266
- const label = typeLabel(effectiveType);
3267
- const ck = constraint.constraintKind;
3268
- switch (ck) {
3269
- case "minimum":
3270
- case "maximum":
3271
- case "exclusiveMinimum":
3272
- case "exclusiveMaximum":
3273
- case "multipleOf": {
3274
- if (!isNumber) {
3275
- addTypeMismatch(
3276
- ctx,
3277
- `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
3278
- constraint.provenance
3279
- );
3280
- }
3281
- break;
3282
- }
3283
- case "minLength":
3284
- case "maxLength":
3285
- case "pattern": {
3286
- if (!isString && !isStringArray) {
3287
- addTypeMismatch(
3288
- ctx,
3289
- `Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
3290
- constraint.provenance
3291
- );
3292
- }
3293
- break;
3294
- }
3295
- case "minItems":
3296
- case "maxItems":
3297
- case "uniqueItems": {
3298
- if (!isArray) {
3299
- addTypeMismatch(
3300
- ctx,
3301
- `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
3302
- constraint.provenance
3303
- );
3304
- }
3305
- break;
3306
- }
3307
- case "allowedMembers": {
3308
- if (!isEnum) {
3309
- addTypeMismatch(
3310
- ctx,
3311
- `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
3312
- constraint.provenance
3313
- );
3314
- }
3315
- break;
3316
- }
3317
- case "const": {
3318
- const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
3319
- effectiveType.primitiveKind
3320
- ) || effectiveType.kind === "enum";
3321
- if (!isPrimitiveConstType) {
3322
- addTypeMismatch(
3323
- ctx,
3324
- `Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
3325
- constraint.provenance
3326
- );
3327
- break;
3328
- }
3329
- if (effectiveType.kind === "primitive") {
3330
- const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
3331
- const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
3332
- if (valueType !== expectedValueType) {
3333
- addTypeMismatch(
3334
- ctx,
3335
- `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
3336
- constraint.provenance
3337
- );
3338
- }
3339
- break;
3340
- }
3341
- const memberValues = effectiveType.members.map((member) => member.value);
3342
- if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
3343
- addTypeMismatch(
3344
- ctx,
3345
- `Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
3346
- constraint.provenance
3347
- );
3348
- }
3349
- break;
3350
- }
3351
- case "custom": {
3352
- checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
3353
- break;
3354
- }
3355
- default: {
3356
- const _exhaustive = constraint;
3357
- throw new Error(
3358
- `Unhandled constraint kind: ${_exhaustive.constraintKind}`
3359
- );
3360
- }
3361
- }
3362
- }
3363
- function checkTypeApplicability(ctx, fieldName, type, constraints) {
3364
- for (const constraint of constraints) {
3365
- if (constraint.path) {
3366
- const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
3367
- const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
3368
- if (resolution.kind === "missing-property") {
3369
- addUnknownPathTarget(
3370
- ctx,
3371
- `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
3372
- constraint.provenance
3373
- );
3374
- continue;
3375
- }
3376
- if (resolution.kind === "unresolvable") {
3377
- addTypeMismatch(
3378
- ctx,
3379
- `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
3380
- constraint.provenance
3381
- );
3382
- continue;
3383
- }
3384
- checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
3385
- continue;
3386
- }
3387
- checkConstraintOnType(ctx, fieldName, type, constraint);
3388
- }
3389
- }
3390
- function checkCustomConstraint(ctx, fieldName, type, constraint) {
3391
- if (ctx.extensionRegistry === void 0) return;
3392
- const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
3393
- if (registration === void 0) {
3394
- addUnknownExtension(
3395
- ctx,
3396
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
3397
- constraint.provenance
3398
- );
3399
- return;
3400
- }
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
- );
3035
+ import {
3036
+ analyzeConstraintTargets
3037
+ } from "@formspec/analysis";
3038
+ function validateFieldNode(ctx, field) {
3039
+ const analysis = analyzeConstraintTargets(
3040
+ field.name,
3041
+ field.type,
3042
+ field.constraints,
3043
+ ctx.typeRegistry,
3044
+ ctx.extensionRegistry === void 0 ? void 0 : {
3045
+ extensionRegistry: ctx.extensionRegistry
3424
3046
  }
3425
- return;
3426
- }
3427
- const applicableTypes = registration.applicableTypes;
3428
- const matchesApplicableType = candidateTypes.some(
3429
- (candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
3430
3047
  );
3431
- if (!matchesApplicableType) {
3432
- addTypeMismatch(
3433
- ctx,
3434
- `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
3435
- constraint.provenance
3436
- );
3437
- }
3438
- }
3439
- function validateFieldNode(ctx, field) {
3440
- validateConstraints(ctx, field.name, field.type, [
3441
- ...collectReferencedTypeConstraints(ctx, field.type),
3442
- ...field.constraints
3443
- ]);
3048
+ ctx.diagnostics.push(...analysis.diagnostics);
3444
3049
  if (field.type.kind === "object") {
3445
- for (const prop of field.type.properties) {
3446
- validateObjectProperty(ctx, field.name, prop);
3050
+ for (const property of field.type.properties) {
3051
+ validateObjectProperty(ctx, field.name, property);
3447
3052
  }
3448
3053
  }
3449
3054
  }
3450
- function validateObjectProperty(ctx, parentName, prop) {
3451
- const qualifiedName = `${parentName}.${prop.name}`;
3452
- validateConstraints(ctx, qualifiedName, prop.type, [
3453
- ...collectReferencedTypeConstraints(ctx, prop.type),
3454
- ...prop.constraints
3455
- ]);
3456
- if (prop.type.kind === "object") {
3457
- for (const nestedProp of prop.type.properties) {
3458
- validateObjectProperty(ctx, qualifiedName, nestedProp);
3055
+ function validateObjectProperty(ctx, parentName, property) {
3056
+ const qualifiedName = `${parentName}.${property.name}`;
3057
+ const analysis = analyzeConstraintTargets(
3058
+ qualifiedName,
3059
+ property.type,
3060
+ property.constraints,
3061
+ ctx.typeRegistry,
3062
+ ctx.extensionRegistry === void 0 ? void 0 : {
3063
+ extensionRegistry: ctx.extensionRegistry
3064
+ }
3065
+ );
3066
+ ctx.diagnostics.push(...analysis.diagnostics);
3067
+ if (property.type.kind === "object") {
3068
+ for (const nestedProperty of property.type.properties) {
3069
+ validateObjectProperty(ctx, qualifiedName, nestedProperty);
3459
3070
  }
3460
3071
  }
3461
3072
  }
3462
- function validateConstraints(ctx, name, type, constraints) {
3463
- checkNumericContradictions(ctx, name, constraints);
3464
- checkLengthContradictions(ctx, name, constraints);
3465
- checkAllowedMembersContradiction(ctx, name, constraints);
3466
- checkConstContradictions(ctx, name, constraints);
3467
- checkConstraintBroadening(ctx, name, constraints);
3468
- checkCustomConstraintSemantics(ctx, name, constraints);
3469
- checkTypeApplicability(ctx, name, type, constraints);
3470
- }
3471
3073
  function validateElement(ctx, element) {
3472
3074
  switch (element.kind) {
3473
3075
  case "field":
@@ -3484,8 +3086,8 @@ function validateElement(ctx, element) {
3484
3086
  }
3485
3087
  break;
3486
3088
  default: {
3487
- const _exhaustive = element;
3488
- throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
3089
+ const exhaustive = element;
3090
+ throw new Error(`Unhandled element kind: ${String(exhaustive)}`);
3489
3091
  }
3490
3092
  }
3491
3093
  }
@@ -3500,12 +3102,18 @@ function validateIR(ir, options) {
3500
3102
  }
3501
3103
  return {
3502
3104
  diagnostics: ctx.diagnostics,
3503
- valid: ctx.diagnostics.every((d) => d.severity !== "error")
3105
+ valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
3504
3106
  };
3505
3107
  }
3506
3108
 
3507
3109
  // src/generators/class-schema.ts
3508
3110
  function generateClassSchemas(analysis, source, options) {
3111
+ const errorDiagnostics = analysis.diagnostics?.filter(
3112
+ (diagnostic) => diagnostic.severity === "error"
3113
+ );
3114
+ if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3115
+ throw new Error(formatValidationError(errorDiagnostics));
3116
+ }
3509
3117
  const ir = canonicalizeTSDoc(analysis, source);
3510
3118
  const validationResult = validateIR(ir, {
3511
3119
  ...options?.extensionRegistry !== void 0 && {