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

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 (68) hide show
  1. package/dist/__tests__/extension-runtime.integration.test.d.ts +2 -0
  2. package/dist/__tests__/extension-runtime.integration.test.d.ts.map +1 -0
  3. package/dist/__tests__/fixtures/edge-cases.d.ts +22 -0
  4. package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures/example-a-builtins.d.ts +6 -6
  6. package/dist/__tests__/fixtures/example-interface-types.d.ts +26 -26
  7. package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -1
  8. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +30 -0
  9. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
  10. package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
  11. package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
  12. package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts +19 -0
  13. package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts.map +1 -0
  14. package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts +6 -0
  15. package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts.map +1 -0
  16. package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts +17 -0
  17. package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts.map +1 -0
  18. package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts +9 -0
  19. package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts.map +1 -0
  20. package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts +6 -0
  21. package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts.map +1 -0
  22. package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts +19 -0
  23. package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts.map +1 -0
  24. package/dist/__tests__/parity/utils.d.ts +11 -4
  25. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  26. package/dist/analyzer/class-analyzer.d.ts +5 -3
  27. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  28. package/dist/analyzer/jsdoc-constraints.d.ts +7 -51
  29. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  30. package/dist/analyzer/tsdoc-parser.d.ts +25 -9
  31. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  32. package/dist/browser.cjs +546 -102
  33. package/dist/browser.cjs.map +1 -1
  34. package/dist/browser.d.ts +15 -2
  35. package/dist/browser.d.ts.map +1 -1
  36. package/dist/browser.js +544 -102
  37. package/dist/browser.js.map +1 -1
  38. package/dist/build.d.ts +170 -6
  39. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
  40. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  41. package/dist/cli.cjs +877 -128
  42. package/dist/cli.cjs.map +1 -1
  43. package/dist/cli.js +876 -131
  44. package/dist/cli.js.map +1 -1
  45. package/dist/generators/mixed-authoring.d.ts +45 -0
  46. package/dist/generators/mixed-authoring.d.ts.map +1 -0
  47. package/dist/index.cjs +850 -125
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.d.ts +22 -3
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +847 -129
  52. package/dist/index.js.map +1 -1
  53. package/dist/internals.cjs +946 -187
  54. package/dist/internals.cjs.map +1 -1
  55. package/dist/internals.js +944 -189
  56. package/dist/internals.js.map +1 -1
  57. package/dist/json-schema/generator.d.ts +8 -2
  58. package/dist/json-schema/generator.d.ts.map +1 -1
  59. package/dist/json-schema/ir-generator.d.ts +27 -4
  60. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  61. package/dist/json-schema/types.d.ts +1 -1
  62. package/dist/json-schema/types.d.ts.map +1 -1
  63. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  64. package/dist/validate/constraint-validator.d.ts +3 -7
  65. package/dist/validate/constraint-validator.d.ts.map +1 -1
  66. package/package.json +3 -3
  67. package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -10
  68. package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
package/dist/cli.js CHANGED
@@ -182,7 +182,7 @@ function canonicalizeArrayField(field) {
182
182
  const itemsType = {
183
183
  kind: "object",
184
184
  properties: itemProperties,
185
- additionalProperties: false
185
+ additionalProperties: true
186
186
  };
187
187
  const type = { kind: "array", items: itemsType };
188
188
  const constraints = [];
@@ -217,7 +217,7 @@ function canonicalizeObjectField(field) {
217
217
  const type = {
218
218
  kind: "object",
219
219
  properties,
220
- additionalProperties: false
220
+ additionalProperties: true
221
221
  };
222
222
  return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
223
223
  }
@@ -342,6 +342,7 @@ function canonicalizeTSDoc(analysis, source) {
342
342
  irVersion: IR_VERSION2,
343
343
  elements,
344
344
  typeRegistry: analysis.typeRegistry,
345
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
345
346
  provenance
346
347
  };
347
348
  }
@@ -415,13 +416,26 @@ var init_canonicalize = __esm({
415
416
  });
416
417
 
417
418
  // src/json-schema/ir-generator.ts
418
- function makeContext() {
419
- return { defs: {} };
419
+ function makeContext(options) {
420
+ const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
421
+ if (!vendorPrefix.startsWith("x-")) {
422
+ throw new Error(
423
+ `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
424
+ );
425
+ }
426
+ return {
427
+ defs: {},
428
+ extensionRegistry: options?.extensionRegistry,
429
+ vendorPrefix
430
+ };
420
431
  }
421
- function generateJsonSchemaFromIR(ir) {
422
- const ctx = makeContext();
432
+ function generateJsonSchemaFromIR(ir, options) {
433
+ const ctx = makeContext(options);
423
434
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
424
435
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
436
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
437
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
438
+ }
425
439
  }
426
440
  const properties = {};
427
441
  const required = [];
@@ -433,6 +447,9 @@ function generateJsonSchemaFromIR(ir) {
433
447
  properties,
434
448
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
435
449
  };
450
+ if (ir.annotations && ir.annotations.length > 0) {
451
+ applyAnnotations(result, ir.annotations, ctx);
452
+ }
436
453
  if (Object.keys(ctx.defs).length > 0) {
437
454
  result.$defs = ctx.defs;
438
455
  }
@@ -462,25 +479,54 @@ function collectFields(elements, properties, required, ctx) {
462
479
  }
463
480
  function generateFieldSchema(field, ctx) {
464
481
  const schema = generateTypeNode(field.type, ctx);
482
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
465
483
  const directConstraints = [];
484
+ const itemConstraints = [];
466
485
  const pathConstraints = [];
467
486
  for (const c of field.constraints) {
468
487
  if (c.path) {
469
488
  pathConstraints.push(c);
489
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
490
+ itemConstraints.push(c);
470
491
  } else {
471
492
  directConstraints.push(c);
472
493
  }
473
494
  }
474
- applyConstraints(schema, directConstraints);
475
- applyAnnotations(schema, field.annotations);
495
+ applyConstraints(schema, directConstraints, ctx);
496
+ if (itemStringSchema !== void 0) {
497
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
498
+ }
499
+ const rootAnnotations = [];
500
+ const itemAnnotations = [];
501
+ for (const annotation of field.annotations) {
502
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
503
+ itemAnnotations.push(annotation);
504
+ } else {
505
+ rootAnnotations.push(annotation);
506
+ }
507
+ }
508
+ applyAnnotations(schema, rootAnnotations, ctx);
509
+ if (itemStringSchema !== void 0) {
510
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
511
+ }
476
512
  if (pathConstraints.length === 0) {
477
513
  return schema;
478
514
  }
479
- return applyPathTargetedConstraints(schema, pathConstraints);
515
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx);
480
516
  }
481
- function applyPathTargetedConstraints(schema, pathConstraints) {
517
+ function isStringItemConstraint(constraint) {
518
+ switch (constraint.constraintKind) {
519
+ case "minLength":
520
+ case "maxLength":
521
+ case "pattern":
522
+ return true;
523
+ default:
524
+ return false;
525
+ }
526
+ }
527
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
482
528
  if (schema.type === "array" && schema.items) {
483
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
529
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
484
530
  return schema;
485
531
  }
486
532
  const byTarget = /* @__PURE__ */ new Map();
@@ -494,7 +540,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
494
540
  const propertyOverrides = {};
495
541
  for (const [target, constraints] of byTarget) {
496
542
  const subSchema = {};
497
- applyConstraints(subSchema, constraints);
543
+ applyConstraints(subSchema, constraints, ctx);
498
544
  propertyOverrides[target] = subSchema;
499
545
  }
500
546
  if (schema.$ref) {
@@ -538,6 +584,8 @@ function generateTypeNode(type, ctx) {
538
584
  return generateArrayType(type, ctx);
539
585
  case "object":
540
586
  return generateObjectType(type, ctx);
587
+ case "record":
588
+ return generateRecordType(type, ctx);
541
589
  case "union":
542
590
  return generateUnionType(type, ctx);
543
591
  case "reference":
@@ -545,7 +593,7 @@ function generateTypeNode(type, ctx) {
545
593
  case "dynamic":
546
594
  return generateDynamicType(type);
547
595
  case "custom":
548
- return generateCustomType(type);
596
+ return generateCustomType(type, ctx);
549
597
  default: {
550
598
  const _exhaustive = type;
551
599
  return _exhaustive;
@@ -594,16 +642,27 @@ function generateObjectType(type, ctx) {
594
642
  }
595
643
  return schema;
596
644
  }
645
+ function generateRecordType(type, ctx) {
646
+ return {
647
+ type: "object",
648
+ additionalProperties: generateTypeNode(type.valueType, ctx)
649
+ };
650
+ }
597
651
  function generatePropertySchema(prop, ctx) {
598
652
  const schema = generateTypeNode(prop.type, ctx);
599
- applyConstraints(schema, prop.constraints);
600
- applyAnnotations(schema, prop.annotations);
653
+ applyConstraints(schema, prop.constraints, ctx);
654
+ applyAnnotations(schema, prop.annotations, ctx);
601
655
  return schema;
602
656
  }
603
657
  function generateUnionType(type, ctx) {
604
658
  if (isBooleanUnion(type)) {
605
659
  return { type: "boolean" };
606
660
  }
661
+ if (isNullableUnion(type)) {
662
+ return {
663
+ oneOf: type.members.map((m) => generateTypeNode(m, ctx))
664
+ };
665
+ }
607
666
  return {
608
667
  anyOf: type.members.map((m) => generateTypeNode(m, ctx))
609
668
  };
@@ -613,6 +672,13 @@ function isBooleanUnion(type) {
613
672
  const kinds = type.members.map((m) => m.kind);
614
673
  return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
615
674
  }
675
+ function isNullableUnion(type) {
676
+ if (type.members.length !== 2) return false;
677
+ const nullCount = type.members.filter(
678
+ (m) => m.kind === "primitive" && m.primitiveKind === "null"
679
+ ).length;
680
+ return nullCount === 1;
681
+ }
616
682
  function generateReferenceType(type) {
617
683
  return { $ref: `#/$defs/${type.name}` };
618
684
  }
@@ -633,10 +699,7 @@ function generateDynamicType(type) {
633
699
  "x-formspec-schemaSource": type.sourceKey
634
700
  };
635
701
  }
636
- function generateCustomType(_type) {
637
- return { type: "object" };
638
- }
639
- function applyConstraints(schema, constraints) {
702
+ function applyConstraints(schema, constraints, ctx) {
640
703
  for (const constraint of constraints) {
641
704
  switch (constraint.constraintKind) {
642
705
  case "minimum":
@@ -678,9 +741,13 @@ function applyConstraints(schema, constraints) {
678
741
  case "uniqueItems":
679
742
  schema.uniqueItems = constraint.value;
680
743
  break;
744
+ case "const":
745
+ schema.const = constraint.value;
746
+ break;
681
747
  case "allowedMembers":
682
748
  break;
683
749
  case "custom":
750
+ applyCustomConstraint(schema, constraint, ctx);
684
751
  break;
685
752
  default: {
686
753
  const _exhaustive = constraint;
@@ -689,7 +756,7 @@ function applyConstraints(schema, constraints) {
689
756
  }
690
757
  }
691
758
  }
692
- function applyAnnotations(schema, annotations) {
759
+ function applyAnnotations(schema, annotations, ctx) {
693
760
  for (const annotation of annotations) {
694
761
  switch (annotation.annotationKind) {
695
762
  case "displayName":
@@ -701,14 +768,21 @@ function applyAnnotations(schema, annotations) {
701
768
  case "defaultValue":
702
769
  schema.default = annotation.value;
703
770
  break;
771
+ case "format":
772
+ schema.format = annotation.value;
773
+ break;
704
774
  case "deprecated":
705
775
  schema.deprecated = true;
776
+ if (annotation.message !== void 0 && annotation.message !== "") {
777
+ schema["x-formspec-deprecation-description"] = annotation.message;
778
+ }
706
779
  break;
707
780
  case "placeholder":
708
781
  break;
709
782
  case "formatHint":
710
783
  break;
711
784
  case "custom":
785
+ applyCustomAnnotation(schema, annotation, ctx);
712
786
  break;
713
787
  default: {
714
788
  const _exhaustive = annotation;
@@ -717,6 +791,36 @@ function applyAnnotations(schema, annotations) {
717
791
  }
718
792
  }
719
793
  }
794
+ function generateCustomType(type, ctx) {
795
+ const registration = ctx.extensionRegistry?.findType(type.typeId);
796
+ if (registration === void 0) {
797
+ throw new Error(
798
+ `Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
799
+ );
800
+ }
801
+ return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
802
+ }
803
+ function applyCustomConstraint(schema, constraint, ctx) {
804
+ const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
805
+ if (registration === void 0) {
806
+ throw new Error(
807
+ `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
808
+ );
809
+ }
810
+ Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
811
+ }
812
+ function applyCustomAnnotation(schema, annotation, ctx) {
813
+ const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
814
+ if (registration === void 0) {
815
+ throw new Error(
816
+ `Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
817
+ );
818
+ }
819
+ if (registration.toJsonSchema === void 0) {
820
+ return;
821
+ }
822
+ Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
823
+ }
720
824
  var init_ir_generator = __esm({
721
825
  "src/json-schema/ir-generator.ts"() {
722
826
  "use strict";
@@ -724,9 +828,9 @@ var init_ir_generator = __esm({
724
828
  });
725
829
 
726
830
  // src/json-schema/generator.ts
727
- function generateJsonSchema(form) {
831
+ function generateJsonSchema(form, options) {
728
832
  const ir = canonicalizeChainDSL(form);
729
- return generateJsonSchemaFromIR(ir);
833
+ return generateJsonSchemaFromIR(ir, options);
730
834
  }
731
835
  var init_generator = __esm({
732
836
  "src/json-schema/generator.ts"() {
@@ -877,25 +981,31 @@ function createShowRule(fieldName, value) {
877
981
  }
878
982
  };
879
983
  }
984
+ function flattenConditionSchema(scope, schema) {
985
+ if (schema.allOf === void 0) {
986
+ if (scope === "#") {
987
+ return [schema];
988
+ }
989
+ const fieldName = scope.replace("#/properties/", "");
990
+ return [
991
+ {
992
+ properties: {
993
+ [fieldName]: schema
994
+ }
995
+ }
996
+ ];
997
+ }
998
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
999
+ }
880
1000
  function combineRules(parentRule, childRule) {
881
- const parentCondition = parentRule.condition;
882
- const childCondition = childRule.condition;
883
1001
  return {
884
1002
  effect: "SHOW",
885
1003
  condition: {
886
1004
  scope: "#",
887
1005
  schema: {
888
1006
  allOf: [
889
- {
890
- properties: {
891
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
892
- }
893
- },
894
- {
895
- properties: {
896
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
897
- }
898
- }
1007
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
1008
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
899
1009
  ]
900
1010
  }
901
1011
  }
@@ -903,10 +1013,14 @@ function combineRules(parentRule, childRule) {
903
1013
  }
904
1014
  function fieldNodeToControl(field, parentRule) {
905
1015
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1016
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
906
1017
  const control = {
907
1018
  type: "Control",
908
1019
  scope: fieldToScope(field.name),
909
1020
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1021
+ ...placeholderAnnotation !== void 0 && {
1022
+ options: { placeholder: placeholderAnnotation.value }
1023
+ },
910
1024
  ...parentRule !== void 0 && { rule: parentRule }
911
1025
  };
912
1026
  return control;
@@ -987,6 +1101,61 @@ var init_types = __esm({
987
1101
  }
988
1102
  });
989
1103
 
1104
+ // src/extensions/registry.ts
1105
+ function createExtensionRegistry(extensions) {
1106
+ const typeMap = /* @__PURE__ */ new Map();
1107
+ const constraintMap = /* @__PURE__ */ new Map();
1108
+ const annotationMap = /* @__PURE__ */ new Map();
1109
+ for (const ext of extensions) {
1110
+ if (ext.types !== void 0) {
1111
+ for (const type of ext.types) {
1112
+ const qualifiedId = `${ext.extensionId}/${type.typeName}`;
1113
+ if (typeMap.has(qualifiedId)) {
1114
+ throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1115
+ }
1116
+ typeMap.set(qualifiedId, type);
1117
+ }
1118
+ }
1119
+ if (ext.constraints !== void 0) {
1120
+ for (const constraint of ext.constraints) {
1121
+ const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
1122
+ if (constraintMap.has(qualifiedId)) {
1123
+ throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
1124
+ }
1125
+ constraintMap.set(qualifiedId, constraint);
1126
+ }
1127
+ }
1128
+ if (ext.annotations !== void 0) {
1129
+ for (const annotation of ext.annotations) {
1130
+ const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
1131
+ if (annotationMap.has(qualifiedId)) {
1132
+ throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
1133
+ }
1134
+ annotationMap.set(qualifiedId, annotation);
1135
+ }
1136
+ }
1137
+ }
1138
+ return {
1139
+ extensions,
1140
+ findType: (typeId) => typeMap.get(typeId),
1141
+ findConstraint: (constraintId) => constraintMap.get(constraintId),
1142
+ findAnnotation: (annotationId) => annotationMap.get(annotationId)
1143
+ };
1144
+ }
1145
+ var init_registry = __esm({
1146
+ "src/extensions/registry.ts"() {
1147
+ "use strict";
1148
+ }
1149
+ });
1150
+
1151
+ // src/extensions/index.ts
1152
+ var init_extensions = __esm({
1153
+ "src/extensions/index.ts"() {
1154
+ "use strict";
1155
+ init_registry();
1156
+ }
1157
+ });
1158
+
990
1159
  // src/json-schema/schema.ts
991
1160
  import { z as z3 } from "zod";
992
1161
  var jsonSchemaTypeSchema, jsonSchema7Schema;
@@ -1173,6 +1342,15 @@ function createFormSpecTSDocConfig() {
1173
1342
  })
1174
1343
  );
1175
1344
  }
1345
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1346
+ config.addTagDefinition(
1347
+ new TSDocTagDefinition({
1348
+ tagName: "@" + tagName,
1349
+ syntaxKind: TSDocTagSyntaxKind.BlockTag,
1350
+ allowMultiple: true
1351
+ })
1352
+ );
1353
+ }
1176
1354
  return config;
1177
1355
  }
1178
1356
  function getParser() {
@@ -1182,6 +1360,12 @@ function getParser() {
1182
1360
  function parseTSDocTags(node, file = "") {
1183
1361
  const constraints = [];
1184
1362
  const annotations = [];
1363
+ let displayName;
1364
+ let description;
1365
+ let placeholder;
1366
+ let displayNameProvenance;
1367
+ let descriptionProvenance;
1368
+ let placeholderProvenance;
1185
1369
  const sourceFile = node.getSourceFile();
1186
1370
  const sourceText = sourceFile.getFullText();
1187
1371
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1201,9 +1385,37 @@ function parseTSDocTags(node, file = "") {
1201
1385
  const docComment = parserContext.docComment;
1202
1386
  for (const block of docComment.customBlocks) {
1203
1387
  const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
1388
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1389
+ const text2 = extractBlockText(block).trim();
1390
+ if (text2 === "") continue;
1391
+ const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1392
+ if (tagName === "displayName") {
1393
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1394
+ displayName = text2;
1395
+ displayNameProvenance = provenance2;
1396
+ }
1397
+ } else if (tagName === "format") {
1398
+ annotations.push({
1399
+ kind: "annotation",
1400
+ annotationKind: "format",
1401
+ value: text2,
1402
+ provenance: provenance2
1403
+ });
1404
+ } else {
1405
+ if (tagName === "description" && description === void 0) {
1406
+ description = text2;
1407
+ descriptionProvenance = provenance2;
1408
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1409
+ placeholder = text2;
1410
+ placeholderProvenance = provenance2;
1411
+ }
1412
+ }
1413
+ continue;
1414
+ }
1204
1415
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1205
1416
  const text = extractBlockText(block).trim();
1206
- if (text === "") continue;
1417
+ const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1418
+ if (text === "" && expectedType !== "boolean") continue;
1207
1419
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1208
1420
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1209
1421
  if (constraintNode) {
@@ -1211,14 +1423,47 @@ function parseTSDocTags(node, file = "") {
1211
1423
  }
1212
1424
  }
1213
1425
  if (docComment.deprecatedBlock !== void 0) {
1426
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1214
1427
  annotations.push({
1215
1428
  kind: "annotation",
1216
1429
  annotationKind: "deprecated",
1430
+ ...message !== "" && { message },
1217
1431
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1218
1432
  });
1219
1433
  }
1434
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1435
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1436
+ if (remarks !== "") {
1437
+ description = remarks;
1438
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1439
+ }
1440
+ }
1220
1441
  }
1221
1442
  }
1443
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1444
+ annotations.push({
1445
+ kind: "annotation",
1446
+ annotationKind: "displayName",
1447
+ value: displayName,
1448
+ provenance: displayNameProvenance
1449
+ });
1450
+ }
1451
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1452
+ annotations.push({
1453
+ kind: "annotation",
1454
+ annotationKind: "description",
1455
+ value: description,
1456
+ provenance: descriptionProvenance
1457
+ });
1458
+ }
1459
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1460
+ annotations.push({
1461
+ kind: "annotation",
1462
+ annotationKind: "placeholder",
1463
+ value: placeholder,
1464
+ provenance: placeholderProvenance
1465
+ });
1466
+ }
1222
1467
  const jsDocTagsAll = ts2.getJSDocTags(node);
1223
1468
  for (const tag of jsDocTagsAll) {
1224
1469
  const tagName = normalizeConstraintTagName(tag.tagName.text);
@@ -1227,47 +1472,39 @@ function parseTSDocTags(node, file = "") {
1227
1472
  if (commentText === void 0 || commentText.trim() === "") continue;
1228
1473
  const text = commentText.trim();
1229
1474
  const provenance = provenanceForJSDocTag(tag, file);
1475
+ if (tagName === "defaultValue") {
1476
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1477
+ annotations.push(defaultValueNode);
1478
+ continue;
1479
+ }
1230
1480
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1231
1481
  if (constraintNode) {
1232
1482
  constraints.push(constraintNode);
1233
1483
  }
1234
1484
  }
1485
+ return { constraints, annotations };
1486
+ }
1487
+ function extractDisplayNameMetadata(node) {
1235
1488
  let displayName;
1236
- let description;
1237
- let displayNameTag;
1238
- let descriptionTag;
1239
- for (const tag of jsDocTagsAll) {
1240
- const tagName = tag.tagName.text;
1489
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1490
+ for (const tag of ts2.getJSDocTags(node)) {
1491
+ const tagName = normalizeConstraintTagName(tag.tagName.text);
1492
+ if (tagName !== "displayName") continue;
1241
1493
  const commentText = getTagCommentText(tag);
1242
- if (commentText === void 0 || commentText.trim() === "") {
1494
+ if (commentText === void 0) continue;
1495
+ const text = commentText.trim();
1496
+ if (text === "") continue;
1497
+ const memberTarget = parseMemberTargetDisplayName(text);
1498
+ if (memberTarget) {
1499
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1243
1500
  continue;
1244
1501
  }
1245
- const trimmed = commentText.trim();
1246
- if (tagName === "Field_displayName") {
1247
- displayName = trimmed;
1248
- displayNameTag = tag;
1249
- } else if (tagName === "Field_description") {
1250
- description = trimmed;
1251
- descriptionTag = tag;
1252
- }
1253
- }
1254
- if (displayName !== void 0 && displayNameTag) {
1255
- annotations.push({
1256
- kind: "annotation",
1257
- annotationKind: "displayName",
1258
- value: displayName,
1259
- provenance: provenanceForJSDocTag(displayNameTag, file)
1260
- });
1502
+ displayName ??= text;
1261
1503
  }
1262
- if (description !== void 0 && descriptionTag) {
1263
- annotations.push({
1264
- kind: "annotation",
1265
- annotationKind: "description",
1266
- value: description,
1267
- provenance: provenanceForJSDocTag(descriptionTag, file)
1268
- });
1269
- }
1270
- return { constraints, annotations };
1504
+ return {
1505
+ ...displayName !== void 0 && { displayName },
1506
+ memberDisplayNames
1507
+ };
1271
1508
  }
1272
1509
  function extractPathTarget(text) {
1273
1510
  const trimmed = text.trimStart();
@@ -1331,7 +1568,45 @@ function parseConstraintValue(tagName, text, provenance) {
1331
1568
  }
1332
1569
  return null;
1333
1570
  }
1571
+ if (expectedType === "boolean") {
1572
+ const trimmed = effectiveText.trim();
1573
+ if (trimmed !== "" && trimmed !== "true") {
1574
+ return null;
1575
+ }
1576
+ if (tagName === "uniqueItems") {
1577
+ return {
1578
+ kind: "constraint",
1579
+ constraintKind: "uniqueItems",
1580
+ value: true,
1581
+ ...path4 && { path: path4 },
1582
+ provenance
1583
+ };
1584
+ }
1585
+ return null;
1586
+ }
1334
1587
  if (expectedType === "json") {
1588
+ if (tagName === "const") {
1589
+ const trimmedText = effectiveText.trim();
1590
+ if (trimmedText === "") return null;
1591
+ try {
1592
+ const parsed2 = JSON.parse(trimmedText);
1593
+ return {
1594
+ kind: "constraint",
1595
+ constraintKind: "const",
1596
+ value: parsed2,
1597
+ ...path4 && { path: path4 },
1598
+ provenance
1599
+ };
1600
+ } catch {
1601
+ return {
1602
+ kind: "constraint",
1603
+ constraintKind: "const",
1604
+ value: trimmedText,
1605
+ ...path4 && { path: path4 },
1606
+ provenance
1607
+ };
1608
+ }
1609
+ }
1335
1610
  const parsed = tryParseJson(effectiveText);
1336
1611
  if (!Array.isArray(parsed)) {
1337
1612
  return null;
@@ -1363,6 +1638,34 @@ function parseConstraintValue(tagName, text, provenance) {
1363
1638
  provenance
1364
1639
  };
1365
1640
  }
1641
+ function parseDefaultValueValue(text, provenance) {
1642
+ const trimmed = text.trim();
1643
+ let value;
1644
+ if (trimmed === "null") {
1645
+ value = null;
1646
+ } else if (trimmed === "true") {
1647
+ value = true;
1648
+ } else if (trimmed === "false") {
1649
+ value = false;
1650
+ } else {
1651
+ const parsed = tryParseJson(trimmed);
1652
+ value = parsed !== null ? parsed : trimmed;
1653
+ }
1654
+ return {
1655
+ kind: "annotation",
1656
+ annotationKind: "defaultValue",
1657
+ value,
1658
+ provenance
1659
+ };
1660
+ }
1661
+ function isMemberTargetDisplayName(text) {
1662
+ return parseMemberTargetDisplayName(text) !== null;
1663
+ }
1664
+ function parseMemberTargetDisplayName(text) {
1665
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1666
+ if (!match?.[1] || !match[2]) return null;
1667
+ return { target: match[1], label: match[2].trim() };
1668
+ }
1366
1669
  function provenanceForComment(range, sourceFile, file, tagName) {
1367
1670
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1368
1671
  return {
@@ -1411,17 +1714,12 @@ var init_tsdoc_parser = __esm({
1411
1714
  minItems: "minItems",
1412
1715
  maxItems: "maxItems"
1413
1716
  };
1414
- TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1717
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1415
1718
  }
1416
1719
  });
1417
1720
 
1418
1721
  // src/analyzer/jsdoc-constraints.ts
1419
1722
  import * as ts3 from "typescript";
1420
- import {
1421
- BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
1422
- isBuiltinConstraintName as isBuiltinConstraintName2,
1423
- normalizeConstraintTagName as normalizeConstraintTagName2
1424
- } from "@formspec/core";
1425
1723
  function extractJSDocConstraintNodes(node, file = "") {
1426
1724
  const result = parseTSDocTags(node, file);
1427
1725
  return [...result.constraints];
@@ -1467,7 +1765,6 @@ var init_jsdoc_constraints = __esm({
1467
1765
  "src/analyzer/jsdoc-constraints.ts"() {
1468
1766
  "use strict";
1469
1767
  init_tsdoc_parser();
1470
- init_json_utils();
1471
1768
  }
1472
1769
  });
1473
1770
 
@@ -1484,6 +1781,7 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1484
1781
  const fields = [];
1485
1782
  const fieldLayouts = [];
1486
1783
  const typeRegistry = {};
1784
+ const annotations = extractJSDocAnnotationNodes(classDecl, file);
1487
1785
  const visiting = /* @__PURE__ */ new Set();
1488
1786
  const instanceMethods = [];
1489
1787
  const staticMethods = [];
@@ -1506,12 +1804,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1506
1804
  }
1507
1805
  }
1508
1806
  }
1509
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1807
+ return {
1808
+ name,
1809
+ fields,
1810
+ fieldLayouts,
1811
+ typeRegistry,
1812
+ ...annotations.length > 0 && { annotations },
1813
+ instanceMethods,
1814
+ staticMethods
1815
+ };
1510
1816
  }
1511
1817
  function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1512
1818
  const name = interfaceDecl.name.text;
1513
1819
  const fields = [];
1514
1820
  const typeRegistry = {};
1821
+ const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1515
1822
  const visiting = /* @__PURE__ */ new Set();
1516
1823
  for (const member of interfaceDecl.members) {
1517
1824
  if (ts4.isPropertySignature(member)) {
@@ -1522,7 +1829,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1522
1829
  }
1523
1830
  }
1524
1831
  const fieldLayouts = fields.map(() => ({}));
1525
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1832
+ return {
1833
+ name,
1834
+ fields,
1835
+ fieldLayouts,
1836
+ typeRegistry,
1837
+ ...annotations.length > 0 && { annotations },
1838
+ instanceMethods: [],
1839
+ staticMethods: []
1840
+ };
1526
1841
  }
1527
1842
  function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1528
1843
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
@@ -1537,6 +1852,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1537
1852
  const name = typeAlias.name.text;
1538
1853
  const fields = [];
1539
1854
  const typeRegistry = {};
1855
+ const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1540
1856
  const visiting = /* @__PURE__ */ new Set();
1541
1857
  for (const member of typeAlias.type.members) {
1542
1858
  if (ts4.isPropertySignature(member)) {
@@ -1553,6 +1869,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1553
1869
  fields,
1554
1870
  fieldLayouts: fields.map(() => ({})),
1555
1871
  typeRegistry,
1872
+ ...annotations.length > 0 && { annotations },
1556
1873
  instanceMethods: [],
1557
1874
  staticMethods: []
1558
1875
  }
@@ -1566,18 +1883,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1566
1883
  const tsType = checker.getTypeAtLocation(prop);
1567
1884
  const optional = prop.questionToken !== void 0;
1568
1885
  const provenance = provenanceForNode(prop, file);
1569
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1886
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1570
1887
  const constraints = [];
1571
1888
  if (prop.type) {
1572
1889
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1573
1890
  }
1574
1891
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1575
- const annotations = [];
1892
+ let annotations = [];
1576
1893
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1577
1894
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1578
- if (defaultAnnotation) {
1895
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1579
1896
  annotations.push(defaultAnnotation);
1580
1897
  }
1898
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1581
1899
  return {
1582
1900
  kind: "field",
1583
1901
  name,
@@ -1596,14 +1914,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1596
1914
  const tsType = checker.getTypeAtLocation(prop);
1597
1915
  const optional = prop.questionToken !== void 0;
1598
1916
  const provenance = provenanceForNode(prop, file);
1599
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1917
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1600
1918
  const constraints = [];
1601
1919
  if (prop.type) {
1602
1920
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1603
1921
  }
1604
1922
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1605
- const annotations = [];
1923
+ let annotations = [];
1606
1924
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1925
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1607
1926
  return {
1608
1927
  kind: "field",
1609
1928
  name,
@@ -1614,7 +1933,69 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1614
1933
  provenance
1615
1934
  };
1616
1935
  }
1617
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1936
+ function applyEnumMemberDisplayNames(type, annotations) {
1937
+ if (!annotations.some(
1938
+ (annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
1939
+ )) {
1940
+ return { type, annotations: [...annotations] };
1941
+ }
1942
+ const consumed = /* @__PURE__ */ new Set();
1943
+ const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
1944
+ if (consumed.size === 0) {
1945
+ return { type, annotations: [...annotations] };
1946
+ }
1947
+ return {
1948
+ type: nextType,
1949
+ annotations: annotations.filter((annotation) => !consumed.has(annotation))
1950
+ };
1951
+ }
1952
+ function rewriteEnumDisplayNames(type, annotations, consumed) {
1953
+ switch (type.kind) {
1954
+ case "enum":
1955
+ return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
1956
+ case "union": {
1957
+ return {
1958
+ ...type,
1959
+ members: type.members.map(
1960
+ (member) => rewriteEnumDisplayNames(member, annotations, consumed)
1961
+ )
1962
+ };
1963
+ }
1964
+ default:
1965
+ return type;
1966
+ }
1967
+ }
1968
+ function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
1969
+ const displayNames = /* @__PURE__ */ new Map();
1970
+ for (const annotation of annotations) {
1971
+ if (annotation.annotationKind !== "displayName") continue;
1972
+ const parsed = parseEnumMemberDisplayName(annotation.value);
1973
+ if (!parsed) continue;
1974
+ consumed.add(annotation);
1975
+ const member = type.members.find((m) => String(m.value) === parsed.value);
1976
+ if (!member) continue;
1977
+ displayNames.set(String(member.value), parsed.label);
1978
+ }
1979
+ if (displayNames.size === 0) {
1980
+ return type;
1981
+ }
1982
+ return {
1983
+ ...type,
1984
+ members: type.members.map((member) => {
1985
+ const displayName = displayNames.get(String(member.value));
1986
+ return displayName !== void 0 ? { ...member, displayName } : member;
1987
+ })
1988
+ };
1989
+ }
1990
+ function parseEnumMemberDisplayName(value) {
1991
+ const trimmed = value.trim();
1992
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
1993
+ if (!match?.[1] || !match[2]) return null;
1994
+ const label = match[2].trim();
1995
+ if (label === "") return null;
1996
+ return { value: match[1], label };
1997
+ }
1998
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1618
1999
  if (type.flags & ts4.TypeFlags.String) {
1619
2000
  return { kind: "primitive", primitiveKind: "string" };
1620
2001
  }
@@ -1643,7 +2024,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1643
2024
  };
1644
2025
  }
1645
2026
  if (type.isUnion()) {
1646
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
2027
+ return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1647
2028
  }
1648
2029
  if (checker.isArrayType(type)) {
1649
2030
  return resolveArrayType(type, checker, file, typeRegistry, visiting);
@@ -1653,70 +2034,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1653
2034
  }
1654
2035
  return { kind: "primitive", primitiveKind: "string" };
1655
2036
  }
1656
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
2037
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
2038
+ const typeName = getNamedTypeName(type);
2039
+ const namedDecl = getNamedTypeDeclaration(type);
2040
+ if (typeName && typeName in typeRegistry) {
2041
+ return { kind: "reference", name: typeName, typeArguments: [] };
2042
+ }
1657
2043
  const allTypes = type.types;
1658
2044
  const nonNullTypes = allTypes.filter(
1659
2045
  (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1660
2046
  );
1661
2047
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2048
+ const memberDisplayNames = /* @__PURE__ */ new Map();
2049
+ if (namedDecl) {
2050
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
2051
+ memberDisplayNames.set(value, label);
2052
+ }
2053
+ }
2054
+ if (sourceNode) {
2055
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
2056
+ memberDisplayNames.set(value, label);
2057
+ }
2058
+ }
2059
+ const registerNamed = (result) => {
2060
+ if (!typeName) {
2061
+ return result;
2062
+ }
2063
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2064
+ typeRegistry[typeName] = {
2065
+ name: typeName,
2066
+ type: result,
2067
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2068
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
2069
+ };
2070
+ return { kind: "reference", name: typeName, typeArguments: [] };
2071
+ };
2072
+ const applyMemberLabels = (members2) => members2.map((value) => {
2073
+ const displayName = memberDisplayNames.get(String(value));
2074
+ return displayName !== void 0 ? { value, displayName } : { value };
2075
+ });
1662
2076
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1663
2077
  if (isBooleanUnion2) {
1664
2078
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1665
- if (hasNull) {
1666
- return {
1667
- kind: "union",
1668
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1669
- };
1670
- }
1671
- return boolNode;
2079
+ const result = hasNull ? {
2080
+ kind: "union",
2081
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
2082
+ } : boolNode;
2083
+ return registerNamed(result);
1672
2084
  }
1673
2085
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1674
2086
  if (allStringLiterals && nonNullTypes.length > 0) {
1675
2087
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1676
2088
  const enumNode = {
1677
2089
  kind: "enum",
1678
- members: stringTypes.map((t) => ({ value: t.value }))
2090
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1679
2091
  };
1680
- if (hasNull) {
1681
- return {
1682
- kind: "union",
1683
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1684
- };
1685
- }
1686
- return enumNode;
2092
+ const result = hasNull ? {
2093
+ kind: "union",
2094
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2095
+ } : enumNode;
2096
+ return registerNamed(result);
1687
2097
  }
1688
2098
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1689
2099
  if (allNumberLiterals && nonNullTypes.length > 0) {
1690
2100
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1691
2101
  const enumNode = {
1692
2102
  kind: "enum",
1693
- members: numberTypes.map((t) => ({ value: t.value }))
2103
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1694
2104
  };
1695
- if (hasNull) {
1696
- return {
1697
- kind: "union",
1698
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1699
- };
1700
- }
1701
- return enumNode;
2105
+ const result = hasNull ? {
2106
+ kind: "union",
2107
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2108
+ } : enumNode;
2109
+ return registerNamed(result);
1702
2110
  }
1703
2111
  if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1704
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1705
- if (hasNull) {
1706
- return {
1707
- kind: "union",
1708
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1709
- };
1710
- }
1711
- return inner;
2112
+ const inner = resolveTypeNode(
2113
+ nonNullTypes[0],
2114
+ checker,
2115
+ file,
2116
+ typeRegistry,
2117
+ visiting,
2118
+ sourceNode
2119
+ );
2120
+ const result = hasNull ? {
2121
+ kind: "union",
2122
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2123
+ } : inner;
2124
+ return registerNamed(result);
1712
2125
  }
1713
2126
  const members = nonNullTypes.map(
1714
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2127
+ (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1715
2128
  );
1716
2129
  if (hasNull) {
1717
2130
  members.push({ kind: "primitive", primitiveKind: "null" });
1718
2131
  }
1719
- return { kind: "union", members };
2132
+ return registerNamed({ kind: "union", members });
1720
2133
  }
1721
2134
  function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1722
2135
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
@@ -1724,15 +2137,92 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1724
2137
  const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1725
2138
  return { kind: "array", items };
1726
2139
  }
2140
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2141
+ if (type.getProperties().length > 0) {
2142
+ return null;
2143
+ }
2144
+ const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
2145
+ if (!indexInfo) {
2146
+ return null;
2147
+ }
2148
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2149
+ return { kind: "record", valueType };
2150
+ }
2151
+ function typeNodeContainsReference(type, targetName) {
2152
+ switch (type.kind) {
2153
+ case "reference":
2154
+ return type.name === targetName;
2155
+ case "array":
2156
+ return typeNodeContainsReference(type.items, targetName);
2157
+ case "record":
2158
+ return typeNodeContainsReference(type.valueType, targetName);
2159
+ case "union":
2160
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2161
+ case "object":
2162
+ return type.properties.some(
2163
+ (property) => typeNodeContainsReference(property.type, targetName)
2164
+ );
2165
+ case "primitive":
2166
+ case "enum":
2167
+ case "dynamic":
2168
+ case "custom":
2169
+ return false;
2170
+ default: {
2171
+ const _exhaustive = type;
2172
+ return _exhaustive;
2173
+ }
2174
+ }
2175
+ }
1727
2176
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2177
+ const typeName = getNamedTypeName(type);
2178
+ const namedTypeName = typeName ?? void 0;
2179
+ const namedDecl = getNamedTypeDeclaration(type);
2180
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2181
+ const clearNamedTypeRegistration = () => {
2182
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2183
+ return;
2184
+ }
2185
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2186
+ };
1728
2187
  if (visiting.has(type)) {
2188
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2189
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2190
+ }
1729
2191
  return { kind: "object", properties: [], additionalProperties: false };
1730
2192
  }
2193
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2194
+ typeRegistry[namedTypeName] = {
2195
+ name: namedTypeName,
2196
+ type: RESOLVING_TYPE_PLACEHOLDER,
2197
+ provenance: provenanceForDeclaration(namedDecl, file)
2198
+ };
2199
+ }
1731
2200
  visiting.add(type);
1732
- const typeName = getNamedTypeName(type);
1733
- if (typeName && typeName in typeRegistry) {
2201
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2202
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2203
+ visiting.delete(type);
2204
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2205
+ }
2206
+ }
2207
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2208
+ if (recordNode) {
1734
2209
  visiting.delete(type);
1735
- return { kind: "reference", name: typeName, typeArguments: [] };
2210
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2211
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2212
+ if (!isRecursiveRecord) {
2213
+ clearNamedTypeRegistration();
2214
+ return recordNode;
2215
+ }
2216
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2217
+ typeRegistry[namedTypeName] = {
2218
+ name: namedTypeName,
2219
+ type: recordNode,
2220
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2221
+ provenance: provenanceForDeclaration(namedDecl, file)
2222
+ };
2223
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2224
+ }
2225
+ return recordNode;
1736
2226
  }
1737
2227
  const properties = [];
1738
2228
  const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
@@ -1741,7 +2231,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1741
2231
  if (!declaration) continue;
1742
2232
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1743
2233
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1744
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2234
+ const propTypeNode = resolveTypeNode(
2235
+ propType,
2236
+ checker,
2237
+ file,
2238
+ typeRegistry,
2239
+ visiting,
2240
+ declaration
2241
+ );
1745
2242
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1746
2243
  properties.push({
1747
2244
  name: prop.name,
@@ -1756,15 +2253,17 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1756
2253
  const objectNode = {
1757
2254
  kind: "object",
1758
2255
  properties,
1759
- additionalProperties: false
2256
+ additionalProperties: true
1760
2257
  };
1761
- if (typeName) {
1762
- typeRegistry[typeName] = {
1763
- name: typeName,
2258
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2259
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2260
+ typeRegistry[namedTypeName] = {
2261
+ name: namedTypeName,
1764
2262
  type: objectNode,
1765
- provenance: provenanceForFile(file)
2263
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2264
+ provenance: provenanceForDeclaration(namedDecl, file)
1766
2265
  };
1767
- return { kind: "reference", name: typeName, typeArguments: [] };
2266
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1768
2267
  }
1769
2268
  return objectNode;
1770
2269
  }
@@ -1855,6 +2354,12 @@ function provenanceForNode(node, file) {
1855
2354
  function provenanceForFile(file) {
1856
2355
  return { surface: "tsdoc", file, line: 0, column: 0 };
1857
2356
  }
2357
+ function provenanceForDeclaration(node, file) {
2358
+ if (!node) {
2359
+ return provenanceForFile(file);
2360
+ }
2361
+ return provenanceForNode(node, file);
2362
+ }
1858
2363
  function getNamedTypeName(type) {
1859
2364
  const symbol = type.getSymbol();
1860
2365
  if (symbol?.declarations) {
@@ -1873,6 +2378,20 @@ function getNamedTypeName(type) {
1873
2378
  }
1874
2379
  return null;
1875
2380
  }
2381
+ function getNamedTypeDeclaration(type) {
2382
+ const symbol = type.getSymbol();
2383
+ if (symbol?.declarations) {
2384
+ const decl = symbol.declarations[0];
2385
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2386
+ return decl;
2387
+ }
2388
+ }
2389
+ const aliasSymbol = type.aliasSymbol;
2390
+ if (aliasSymbol?.declarations) {
2391
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2392
+ }
2393
+ return void 0;
2394
+ }
1876
2395
  function analyzeMethod(method, checker) {
1877
2396
  if (!ts4.isIdentifier(method.name)) {
1878
2397
  return null;
@@ -1913,11 +2432,17 @@ function detectFormSpecReference(typeNode) {
1913
2432
  }
1914
2433
  return null;
1915
2434
  }
1916
- var MAX_ALIAS_CHAIN_DEPTH;
2435
+ var RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
1917
2436
  var init_class_analyzer = __esm({
1918
2437
  "src/analyzer/class-analyzer.ts"() {
1919
2438
  "use strict";
1920
2439
  init_jsdoc_constraints();
2440
+ init_tsdoc_parser();
2441
+ RESOLVING_TYPE_PLACEHOLDER = {
2442
+ kind: "object",
2443
+ properties: [],
2444
+ additionalProperties: true
2445
+ };
1921
2446
  MAX_ALIAS_CHAIN_DEPTH = 8;
1922
2447
  }
1923
2448
  });
@@ -1975,14 +2500,226 @@ var init_class_schema = __esm({
1975
2500
  }
1976
2501
  });
1977
2502
 
2503
+ // src/generators/mixed-authoring.ts
2504
+ function buildMixedAuthoringSchemas(options) {
2505
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2506
+ const analysis = analyzeNamedType(filePath, typeName);
2507
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2508
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2509
+ return {
2510
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2511
+ uiSchema: generateUiSchemaFromIR(ir)
2512
+ };
2513
+ }
2514
+ function analyzeNamedType(filePath, typeName) {
2515
+ const ctx = createProgramContext(filePath);
2516
+ const source = { file: filePath };
2517
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2518
+ if (classDecl !== null) {
2519
+ return analyzeClassToIR(classDecl, ctx.checker, source.file);
2520
+ }
2521
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2522
+ if (interfaceDecl !== null) {
2523
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2524
+ }
2525
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2526
+ if (typeAlias !== null) {
2527
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2528
+ if (result.ok) {
2529
+ return result.analysis;
2530
+ }
2531
+ throw new Error(result.error);
2532
+ }
2533
+ throw new Error(
2534
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2535
+ );
2536
+ }
2537
+ function composeAnalysisWithOverlays(analysis, overlays) {
2538
+ const overlayIR = canonicalizeChainDSL(overlays);
2539
+ const overlayFields = collectOverlayFields(overlayIR.elements);
2540
+ if (overlayFields.length === 0) {
2541
+ return analysis;
2542
+ }
2543
+ const overlayByName = /* @__PURE__ */ new Map();
2544
+ for (const field of overlayFields) {
2545
+ if (overlayByName.has(field.name)) {
2546
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
2547
+ }
2548
+ overlayByName.set(field.name, field);
2549
+ }
2550
+ const mergedFields = [];
2551
+ for (const baseField of analysis.fields) {
2552
+ const overlayField = overlayByName.get(baseField.name);
2553
+ if (overlayField === void 0) {
2554
+ mergedFields.push(baseField);
2555
+ continue;
2556
+ }
2557
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
2558
+ overlayByName.delete(baseField.name);
2559
+ }
2560
+ if (overlayByName.size > 0) {
2561
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
2562
+ throw new Error(
2563
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
2564
+ );
2565
+ }
2566
+ return {
2567
+ ...analysis,
2568
+ fields: mergedFields
2569
+ };
2570
+ }
2571
+ function collectOverlayFields(elements) {
2572
+ const fields = [];
2573
+ for (const element of elements) {
2574
+ switch (element.kind) {
2575
+ case "field":
2576
+ fields.push(element);
2577
+ break;
2578
+ case "group":
2579
+ fields.push(...collectOverlayFields(element.elements));
2580
+ break;
2581
+ case "conditional":
2582
+ fields.push(...collectOverlayFields(element.elements));
2583
+ break;
2584
+ default: {
2585
+ const _exhaustive = element;
2586
+ void _exhaustive;
2587
+ }
2588
+ }
2589
+ }
2590
+ return fields;
2591
+ }
2592
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
2593
+ assertSupportedOverlayField(baseField, overlayField);
2594
+ return {
2595
+ ...baseField,
2596
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
2597
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
2598
+ };
2599
+ }
2600
+ function assertSupportedOverlayField(baseField, overlayField) {
2601
+ if (overlayField.constraints.length > 0) {
2602
+ throw new Error(
2603
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2604
+ );
2605
+ }
2606
+ if (overlayField.required) {
2607
+ throw new Error(
2608
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2609
+ );
2610
+ }
2611
+ }
2612
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
2613
+ const { type: baseType } = baseField;
2614
+ const { type: overlayType } = overlayField;
2615
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
2616
+ throw new Error(
2617
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
2618
+ );
2619
+ }
2620
+ if (overlayType.kind === "dynamic") {
2621
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
2622
+ throw new Error(
2623
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
2624
+ );
2625
+ }
2626
+ return overlayType;
2627
+ }
2628
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
2629
+ throw new Error(
2630
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
2631
+ );
2632
+ }
2633
+ return baseType;
2634
+ }
2635
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
2636
+ const overlayType = overlayField.type;
2637
+ if (overlayType.kind !== "dynamic") {
2638
+ return false;
2639
+ }
2640
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
2641
+ if (resolvedBaseType === null) {
2642
+ return false;
2643
+ }
2644
+ if (overlayType.dynamicKind === "enum") {
2645
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
2646
+ }
2647
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
2648
+ }
2649
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
2650
+ if (type.kind !== "reference") {
2651
+ return type;
2652
+ }
2653
+ if (seen.has(type.name)) {
2654
+ return null;
2655
+ }
2656
+ const definition = typeRegistry[type.name];
2657
+ if (definition === void 0) {
2658
+ return null;
2659
+ }
2660
+ seen.add(type.name);
2661
+ return resolveReferenceType(definition.type, typeRegistry, seen);
2662
+ }
2663
+ function isSameStaticTypeShape(baseType, overlayType) {
2664
+ if (baseType.kind !== overlayType.kind) {
2665
+ return false;
2666
+ }
2667
+ switch (baseType.kind) {
2668
+ case "primitive":
2669
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
2670
+ case "enum":
2671
+ return overlayType.kind === "enum";
2672
+ case "dynamic":
2673
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
2674
+ case "record":
2675
+ return overlayType.kind === "record";
2676
+ case "reference":
2677
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
2678
+ case "union":
2679
+ return overlayType.kind === "union";
2680
+ case "custom":
2681
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
2682
+ case "object":
2683
+ case "array":
2684
+ return true;
2685
+ default: {
2686
+ const _exhaustive = baseType;
2687
+ return _exhaustive;
2688
+ }
2689
+ }
2690
+ }
2691
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2692
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
2693
+ const overlayOnly = overlayAnnotations.filter(
2694
+ (annotation) => !baseKeys.has(annotationKey(annotation))
2695
+ );
2696
+ return [...overlayOnly, ...baseAnnotations];
2697
+ }
2698
+ function annotationKey(annotation) {
2699
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
2700
+ }
2701
+ var init_mixed_authoring = __esm({
2702
+ "src/generators/mixed-authoring.ts"() {
2703
+ "use strict";
2704
+ init_ir_generator();
2705
+ init_ir_generator2();
2706
+ init_canonicalize();
2707
+ init_program();
2708
+ init_class_analyzer();
2709
+ }
2710
+ });
2711
+
1978
2712
  // src/index.ts
1979
2713
  var index_exports = {};
1980
2714
  __export(index_exports, {
1981
2715
  buildFormSchemas: () => buildFormSchemas,
2716
+ buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
1982
2717
  categorizationSchema: () => categorizationSchema,
1983
2718
  categorySchema: () => categorySchema,
1984
2719
  controlSchema: () => controlSchema,
2720
+ createExtensionRegistry: () => createExtensionRegistry,
1985
2721
  generateJsonSchema: () => generateJsonSchema,
2722
+ generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
1986
2723
  generateSchemas: () => generateSchemas,
1987
2724
  generateSchemasFromClass: () => generateSchemasFromClass,
1988
2725
  generateUiSchema: () => generateUiSchema,
@@ -2005,15 +2742,19 @@ __export(index_exports, {
2005
2742
  });
2006
2743
  import * as fs from "fs";
2007
2744
  import * as path2 from "path";
2008
- function buildFormSchemas(form) {
2745
+ function buildFormSchemas(form, options) {
2009
2746
  return {
2010
- jsonSchema: generateJsonSchema(form),
2747
+ jsonSchema: generateJsonSchema(form, options),
2011
2748
  uiSchema: generateUiSchema(form)
2012
2749
  };
2013
2750
  }
2014
2751
  function writeSchemas(form, options) {
2015
- const { outDir, name = "schema", indent = 2 } = options;
2016
- const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
2752
+ const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
2753
+ const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
2754
+ extensionRegistry,
2755
+ vendorPrefix
2756
+ };
2757
+ const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
2017
2758
  if (!fs.existsSync(outDir)) {
2018
2759
  fs.mkdirSync(outDir, { recursive: true });
2019
2760
  }
@@ -2028,12 +2769,16 @@ var init_index = __esm({
2028
2769
  "use strict";
2029
2770
  init_generator();
2030
2771
  init_generator2();
2772
+ init_ir_generator();
2031
2773
  init_types();
2774
+ init_extensions();
2032
2775
  init_schema();
2033
2776
  init_schema2();
2034
2777
  init_generator();
2778
+ init_ir_generator();
2035
2779
  init_generator2();
2036
2780
  init_class_schema();
2781
+ init_mixed_authoring();
2037
2782
  }
2038
2783
  });
2039
2784