@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/index.cjs CHANGED
@@ -31,10 +31,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  buildFormSchemas: () => buildFormSchemas,
34
+ buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
34
35
  categorizationSchema: () => categorizationSchema,
35
36
  categorySchema: () => categorySchema,
36
37
  controlSchema: () => controlSchema,
38
+ createExtensionRegistry: () => createExtensionRegistry,
37
39
  generateJsonSchema: () => generateJsonSchema,
40
+ generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
38
41
  generateSchemas: () => generateSchemas,
39
42
  generateSchemasFromClass: () => generateSchemasFromClass,
40
43
  generateUiSchema: () => generateUiSchema,
@@ -236,7 +239,7 @@ function canonicalizeArrayField(field) {
236
239
  const itemsType = {
237
240
  kind: "object",
238
241
  properties: itemProperties,
239
- additionalProperties: false
242
+ additionalProperties: true
240
243
  };
241
244
  const type = { kind: "array", items: itemsType };
242
245
  const constraints = [];
@@ -271,7 +274,7 @@ function canonicalizeObjectField(field) {
271
274
  const type = {
272
275
  kind: "object",
273
276
  properties,
274
- additionalProperties: false
277
+ additionalProperties: true
275
278
  };
276
279
  return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
277
280
  }
@@ -384,6 +387,7 @@ function canonicalizeTSDoc(analysis, source) {
384
387
  irVersion: import_core2.IR_VERSION,
385
388
  elements,
386
389
  typeRegistry: analysis.typeRegistry,
390
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
387
391
  provenance
388
392
  };
389
393
  }
@@ -443,13 +447,26 @@ function wrapInConditional(field, layout, provenance) {
443
447
  }
444
448
 
445
449
  // src/json-schema/ir-generator.ts
446
- function makeContext() {
447
- return { defs: {} };
450
+ function makeContext(options) {
451
+ const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
452
+ if (!vendorPrefix.startsWith("x-")) {
453
+ throw new Error(
454
+ `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
455
+ );
456
+ }
457
+ return {
458
+ defs: {},
459
+ extensionRegistry: options?.extensionRegistry,
460
+ vendorPrefix
461
+ };
448
462
  }
449
- function generateJsonSchemaFromIR(ir) {
450
- const ctx = makeContext();
463
+ function generateJsonSchemaFromIR(ir, options) {
464
+ const ctx = makeContext(options);
451
465
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
452
466
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
467
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
468
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
469
+ }
453
470
  }
454
471
  const properties = {};
455
472
  const required = [];
@@ -461,6 +478,9 @@ function generateJsonSchemaFromIR(ir) {
461
478
  properties,
462
479
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
463
480
  };
481
+ if (ir.annotations && ir.annotations.length > 0) {
482
+ applyAnnotations(result, ir.annotations, ctx);
483
+ }
464
484
  if (Object.keys(ctx.defs).length > 0) {
465
485
  result.$defs = ctx.defs;
466
486
  }
@@ -490,25 +510,54 @@ function collectFields(elements, properties, required, ctx) {
490
510
  }
491
511
  function generateFieldSchema(field, ctx) {
492
512
  const schema = generateTypeNode(field.type, ctx);
513
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
493
514
  const directConstraints = [];
515
+ const itemConstraints = [];
494
516
  const pathConstraints = [];
495
517
  for (const c of field.constraints) {
496
518
  if (c.path) {
497
519
  pathConstraints.push(c);
520
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
521
+ itemConstraints.push(c);
498
522
  } else {
499
523
  directConstraints.push(c);
500
524
  }
501
525
  }
502
- applyConstraints(schema, directConstraints);
503
- applyAnnotations(schema, field.annotations);
526
+ applyConstraints(schema, directConstraints, ctx);
527
+ if (itemStringSchema !== void 0) {
528
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
529
+ }
530
+ const rootAnnotations = [];
531
+ const itemAnnotations = [];
532
+ for (const annotation of field.annotations) {
533
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
534
+ itemAnnotations.push(annotation);
535
+ } else {
536
+ rootAnnotations.push(annotation);
537
+ }
538
+ }
539
+ applyAnnotations(schema, rootAnnotations, ctx);
540
+ if (itemStringSchema !== void 0) {
541
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
542
+ }
504
543
  if (pathConstraints.length === 0) {
505
544
  return schema;
506
545
  }
507
- return applyPathTargetedConstraints(schema, pathConstraints);
546
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx);
508
547
  }
509
- function applyPathTargetedConstraints(schema, pathConstraints) {
548
+ function isStringItemConstraint(constraint) {
549
+ switch (constraint.constraintKind) {
550
+ case "minLength":
551
+ case "maxLength":
552
+ case "pattern":
553
+ return true;
554
+ default:
555
+ return false;
556
+ }
557
+ }
558
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
510
559
  if (schema.type === "array" && schema.items) {
511
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
560
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
512
561
  return schema;
513
562
  }
514
563
  const byTarget = /* @__PURE__ */ new Map();
@@ -522,7 +571,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
522
571
  const propertyOverrides = {};
523
572
  for (const [target, constraints] of byTarget) {
524
573
  const subSchema = {};
525
- applyConstraints(subSchema, constraints);
574
+ applyConstraints(subSchema, constraints, ctx);
526
575
  propertyOverrides[target] = subSchema;
527
576
  }
528
577
  if (schema.$ref) {
@@ -566,6 +615,8 @@ function generateTypeNode(type, ctx) {
566
615
  return generateArrayType(type, ctx);
567
616
  case "object":
568
617
  return generateObjectType(type, ctx);
618
+ case "record":
619
+ return generateRecordType(type, ctx);
569
620
  case "union":
570
621
  return generateUnionType(type, ctx);
571
622
  case "reference":
@@ -573,7 +624,7 @@ function generateTypeNode(type, ctx) {
573
624
  case "dynamic":
574
625
  return generateDynamicType(type);
575
626
  case "custom":
576
- return generateCustomType(type);
627
+ return generateCustomType(type, ctx);
577
628
  default: {
578
629
  const _exhaustive = type;
579
630
  return _exhaustive;
@@ -622,16 +673,27 @@ function generateObjectType(type, ctx) {
622
673
  }
623
674
  return schema;
624
675
  }
676
+ function generateRecordType(type, ctx) {
677
+ return {
678
+ type: "object",
679
+ additionalProperties: generateTypeNode(type.valueType, ctx)
680
+ };
681
+ }
625
682
  function generatePropertySchema(prop, ctx) {
626
683
  const schema = generateTypeNode(prop.type, ctx);
627
- applyConstraints(schema, prop.constraints);
628
- applyAnnotations(schema, prop.annotations);
684
+ applyConstraints(schema, prop.constraints, ctx);
685
+ applyAnnotations(schema, prop.annotations, ctx);
629
686
  return schema;
630
687
  }
631
688
  function generateUnionType(type, ctx) {
632
689
  if (isBooleanUnion(type)) {
633
690
  return { type: "boolean" };
634
691
  }
692
+ if (isNullableUnion(type)) {
693
+ return {
694
+ oneOf: type.members.map((m) => generateTypeNode(m, ctx))
695
+ };
696
+ }
635
697
  return {
636
698
  anyOf: type.members.map((m) => generateTypeNode(m, ctx))
637
699
  };
@@ -641,6 +703,13 @@ function isBooleanUnion(type) {
641
703
  const kinds = type.members.map((m) => m.kind);
642
704
  return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
643
705
  }
706
+ function isNullableUnion(type) {
707
+ if (type.members.length !== 2) return false;
708
+ const nullCount = type.members.filter(
709
+ (m) => m.kind === "primitive" && m.primitiveKind === "null"
710
+ ).length;
711
+ return nullCount === 1;
712
+ }
644
713
  function generateReferenceType(type) {
645
714
  return { $ref: `#/$defs/${type.name}` };
646
715
  }
@@ -661,10 +730,7 @@ function generateDynamicType(type) {
661
730
  "x-formspec-schemaSource": type.sourceKey
662
731
  };
663
732
  }
664
- function generateCustomType(_type) {
665
- return { type: "object" };
666
- }
667
- function applyConstraints(schema, constraints) {
733
+ function applyConstraints(schema, constraints, ctx) {
668
734
  for (const constraint of constraints) {
669
735
  switch (constraint.constraintKind) {
670
736
  case "minimum":
@@ -706,9 +772,13 @@ function applyConstraints(schema, constraints) {
706
772
  case "uniqueItems":
707
773
  schema.uniqueItems = constraint.value;
708
774
  break;
775
+ case "const":
776
+ schema.const = constraint.value;
777
+ break;
709
778
  case "allowedMembers":
710
779
  break;
711
780
  case "custom":
781
+ applyCustomConstraint(schema, constraint, ctx);
712
782
  break;
713
783
  default: {
714
784
  const _exhaustive = constraint;
@@ -717,7 +787,7 @@ function applyConstraints(schema, constraints) {
717
787
  }
718
788
  }
719
789
  }
720
- function applyAnnotations(schema, annotations) {
790
+ function applyAnnotations(schema, annotations, ctx) {
721
791
  for (const annotation of annotations) {
722
792
  switch (annotation.annotationKind) {
723
793
  case "displayName":
@@ -729,14 +799,21 @@ function applyAnnotations(schema, annotations) {
729
799
  case "defaultValue":
730
800
  schema.default = annotation.value;
731
801
  break;
802
+ case "format":
803
+ schema.format = annotation.value;
804
+ break;
732
805
  case "deprecated":
733
806
  schema.deprecated = true;
807
+ if (annotation.message !== void 0 && annotation.message !== "") {
808
+ schema["x-formspec-deprecation-description"] = annotation.message;
809
+ }
734
810
  break;
735
811
  case "placeholder":
736
812
  break;
737
813
  case "formatHint":
738
814
  break;
739
815
  case "custom":
816
+ applyCustomAnnotation(schema, annotation, ctx);
740
817
  break;
741
818
  default: {
742
819
  const _exhaustive = annotation;
@@ -745,11 +822,41 @@ function applyAnnotations(schema, annotations) {
745
822
  }
746
823
  }
747
824
  }
825
+ function generateCustomType(type, ctx) {
826
+ const registration = ctx.extensionRegistry?.findType(type.typeId);
827
+ if (registration === void 0) {
828
+ throw new Error(
829
+ `Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
830
+ );
831
+ }
832
+ return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
833
+ }
834
+ function applyCustomConstraint(schema, constraint, ctx) {
835
+ const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
836
+ if (registration === void 0) {
837
+ throw new Error(
838
+ `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
839
+ );
840
+ }
841
+ Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
842
+ }
843
+ function applyCustomAnnotation(schema, annotation, ctx) {
844
+ const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
845
+ if (registration === void 0) {
846
+ throw new Error(
847
+ `Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
848
+ );
849
+ }
850
+ if (registration.toJsonSchema === void 0) {
851
+ return;
852
+ }
853
+ Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
854
+ }
748
855
 
749
856
  // src/json-schema/generator.ts
750
- function generateJsonSchema(form) {
857
+ function generateJsonSchema(form, options) {
751
858
  const ir = canonicalizeChainDSL(form);
752
- return generateJsonSchemaFromIR(ir);
859
+ return generateJsonSchemaFromIR(ir, options);
753
860
  }
754
861
 
755
862
  // src/ui-schema/schema.ts
@@ -887,25 +994,31 @@ function createShowRule(fieldName, value) {
887
994
  }
888
995
  };
889
996
  }
997
+ function flattenConditionSchema(scope, schema) {
998
+ if (schema.allOf === void 0) {
999
+ if (scope === "#") {
1000
+ return [schema];
1001
+ }
1002
+ const fieldName = scope.replace("#/properties/", "");
1003
+ return [
1004
+ {
1005
+ properties: {
1006
+ [fieldName]: schema
1007
+ }
1008
+ }
1009
+ ];
1010
+ }
1011
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
1012
+ }
890
1013
  function combineRules(parentRule, childRule) {
891
- const parentCondition = parentRule.condition;
892
- const childCondition = childRule.condition;
893
1014
  return {
894
1015
  effect: "SHOW",
895
1016
  condition: {
896
1017
  scope: "#",
897
1018
  schema: {
898
1019
  allOf: [
899
- {
900
- properties: {
901
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
902
- }
903
- },
904
- {
905
- properties: {
906
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
907
- }
908
- }
1020
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
1021
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
909
1022
  ]
910
1023
  }
911
1024
  }
@@ -913,10 +1026,14 @@ function combineRules(parentRule, childRule) {
913
1026
  }
914
1027
  function fieldNodeToControl(field, parentRule) {
915
1028
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1029
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
916
1030
  const control = {
917
1031
  type: "Control",
918
1032
  scope: fieldToScope(field.name),
919
1033
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1034
+ ...placeholderAnnotation !== void 0 && {
1035
+ options: { placeholder: placeholderAnnotation.value }
1036
+ },
920
1037
  ...parentRule !== void 0 && { rule: parentRule }
921
1038
  };
922
1039
  return control;
@@ -983,6 +1100,48 @@ function getSchemaExtension(schema, key) {
983
1100
  return schema[key];
984
1101
  }
985
1102
 
1103
+ // src/extensions/registry.ts
1104
+ function createExtensionRegistry(extensions) {
1105
+ const typeMap = /* @__PURE__ */ new Map();
1106
+ const constraintMap = /* @__PURE__ */ new Map();
1107
+ const annotationMap = /* @__PURE__ */ new Map();
1108
+ for (const ext of extensions) {
1109
+ if (ext.types !== void 0) {
1110
+ for (const type of ext.types) {
1111
+ const qualifiedId = `${ext.extensionId}/${type.typeName}`;
1112
+ if (typeMap.has(qualifiedId)) {
1113
+ throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1114
+ }
1115
+ typeMap.set(qualifiedId, type);
1116
+ }
1117
+ }
1118
+ if (ext.constraints !== void 0) {
1119
+ for (const constraint of ext.constraints) {
1120
+ const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
1121
+ if (constraintMap.has(qualifiedId)) {
1122
+ throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
1123
+ }
1124
+ constraintMap.set(qualifiedId, constraint);
1125
+ }
1126
+ }
1127
+ if (ext.annotations !== void 0) {
1128
+ for (const annotation of ext.annotations) {
1129
+ const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
1130
+ if (annotationMap.has(qualifiedId)) {
1131
+ throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
1132
+ }
1133
+ annotationMap.set(qualifiedId, annotation);
1134
+ }
1135
+ }
1136
+ }
1137
+ return {
1138
+ extensions,
1139
+ findType: (typeId) => typeMap.get(typeId),
1140
+ findConstraint: (constraintId) => constraintMap.get(constraintId),
1141
+ findAnnotation: (annotationId) => annotationMap.get(annotationId)
1142
+ };
1143
+ }
1144
+
986
1145
  // src/json-schema/schema.ts
987
1146
  var import_zod3 = require("zod");
988
1147
  var jsonSchemaTypeSchema = import_zod3.z.enum([
@@ -1122,7 +1281,6 @@ var ts4 = __toESM(require("typescript"), 1);
1122
1281
 
1123
1282
  // src/analyzer/jsdoc-constraints.ts
1124
1283
  var ts3 = __toESM(require("typescript"), 1);
1125
- var import_core4 = require("@formspec/core");
1126
1284
 
1127
1285
  // src/analyzer/tsdoc-parser.ts
1128
1286
  var ts2 = __toESM(require("typescript"), 1);
@@ -1152,7 +1310,7 @@ var LENGTH_CONSTRAINT_MAP = {
1152
1310
  minItems: "minItems",
1153
1311
  maxItems: "maxItems"
1154
1312
  };
1155
- var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1313
+ var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1156
1314
  function createFormSpecTSDocConfig() {
1157
1315
  const config = new import_tsdoc.TSDocConfiguration();
1158
1316
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
@@ -1164,6 +1322,15 @@ function createFormSpecTSDocConfig() {
1164
1322
  })
1165
1323
  );
1166
1324
  }
1325
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1326
+ config.addTagDefinition(
1327
+ new import_tsdoc.TSDocTagDefinition({
1328
+ tagName: "@" + tagName,
1329
+ syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
1330
+ allowMultiple: true
1331
+ })
1332
+ );
1333
+ }
1167
1334
  return config;
1168
1335
  }
1169
1336
  var sharedParser;
@@ -1174,6 +1341,12 @@ function getParser() {
1174
1341
  function parseTSDocTags(node, file = "") {
1175
1342
  const constraints = [];
1176
1343
  const annotations = [];
1344
+ let displayName;
1345
+ let description;
1346
+ let placeholder;
1347
+ let displayNameProvenance;
1348
+ let descriptionProvenance;
1349
+ let placeholderProvenance;
1177
1350
  const sourceFile = node.getSourceFile();
1178
1351
  const sourceText = sourceFile.getFullText();
1179
1352
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1193,9 +1366,37 @@ function parseTSDocTags(node, file = "") {
1193
1366
  const docComment = parserContext.docComment;
1194
1367
  for (const block of docComment.customBlocks) {
1195
1368
  const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
1369
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1370
+ const text2 = extractBlockText(block).trim();
1371
+ if (text2 === "") continue;
1372
+ const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1373
+ if (tagName === "displayName") {
1374
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1375
+ displayName = text2;
1376
+ displayNameProvenance = provenance2;
1377
+ }
1378
+ } else if (tagName === "format") {
1379
+ annotations.push({
1380
+ kind: "annotation",
1381
+ annotationKind: "format",
1382
+ value: text2,
1383
+ provenance: provenance2
1384
+ });
1385
+ } else {
1386
+ if (tagName === "description" && description === void 0) {
1387
+ description = text2;
1388
+ descriptionProvenance = provenance2;
1389
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1390
+ placeholder = text2;
1391
+ placeholderProvenance = provenance2;
1392
+ }
1393
+ }
1394
+ continue;
1395
+ }
1196
1396
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1197
1397
  const text = extractBlockText(block).trim();
1198
- if (text === "") continue;
1398
+ const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1399
+ if (text === "" && expectedType !== "boolean") continue;
1199
1400
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1200
1401
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1201
1402
  if (constraintNode) {
@@ -1203,14 +1404,47 @@ function parseTSDocTags(node, file = "") {
1203
1404
  }
1204
1405
  }
1205
1406
  if (docComment.deprecatedBlock !== void 0) {
1407
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1206
1408
  annotations.push({
1207
1409
  kind: "annotation",
1208
1410
  annotationKind: "deprecated",
1411
+ ...message !== "" && { message },
1209
1412
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1210
1413
  });
1211
1414
  }
1415
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1416
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1417
+ if (remarks !== "") {
1418
+ description = remarks;
1419
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1420
+ }
1421
+ }
1212
1422
  }
1213
1423
  }
1424
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1425
+ annotations.push({
1426
+ kind: "annotation",
1427
+ annotationKind: "displayName",
1428
+ value: displayName,
1429
+ provenance: displayNameProvenance
1430
+ });
1431
+ }
1432
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1433
+ annotations.push({
1434
+ kind: "annotation",
1435
+ annotationKind: "description",
1436
+ value: description,
1437
+ provenance: descriptionProvenance
1438
+ });
1439
+ }
1440
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1441
+ annotations.push({
1442
+ kind: "annotation",
1443
+ annotationKind: "placeholder",
1444
+ value: placeholder,
1445
+ provenance: placeholderProvenance
1446
+ });
1447
+ }
1214
1448
  const jsDocTagsAll = ts2.getJSDocTags(node);
1215
1449
  for (const tag of jsDocTagsAll) {
1216
1450
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
@@ -1219,47 +1453,39 @@ function parseTSDocTags(node, file = "") {
1219
1453
  if (commentText === void 0 || commentText.trim() === "") continue;
1220
1454
  const text = commentText.trim();
1221
1455
  const provenance = provenanceForJSDocTag(tag, file);
1456
+ if (tagName === "defaultValue") {
1457
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1458
+ annotations.push(defaultValueNode);
1459
+ continue;
1460
+ }
1222
1461
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1223
1462
  if (constraintNode) {
1224
1463
  constraints.push(constraintNode);
1225
1464
  }
1226
1465
  }
1466
+ return { constraints, annotations };
1467
+ }
1468
+ function extractDisplayNameMetadata(node) {
1227
1469
  let displayName;
1228
- let description;
1229
- let displayNameTag;
1230
- let descriptionTag;
1231
- for (const tag of jsDocTagsAll) {
1232
- const tagName = tag.tagName.text;
1470
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1471
+ for (const tag of ts2.getJSDocTags(node)) {
1472
+ const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1473
+ if (tagName !== "displayName") continue;
1233
1474
  const commentText = getTagCommentText(tag);
1234
- if (commentText === void 0 || commentText.trim() === "") {
1475
+ if (commentText === void 0) continue;
1476
+ const text = commentText.trim();
1477
+ if (text === "") continue;
1478
+ const memberTarget = parseMemberTargetDisplayName(text);
1479
+ if (memberTarget) {
1480
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1235
1481
  continue;
1236
1482
  }
1237
- const trimmed = commentText.trim();
1238
- if (tagName === "Field_displayName") {
1239
- displayName = trimmed;
1240
- displayNameTag = tag;
1241
- } else if (tagName === "Field_description") {
1242
- description = trimmed;
1243
- descriptionTag = tag;
1244
- }
1483
+ displayName ??= text;
1245
1484
  }
1246
- if (displayName !== void 0 && displayNameTag) {
1247
- annotations.push({
1248
- kind: "annotation",
1249
- annotationKind: "displayName",
1250
- value: displayName,
1251
- provenance: provenanceForJSDocTag(displayNameTag, file)
1252
- });
1253
- }
1254
- if (description !== void 0 && descriptionTag) {
1255
- annotations.push({
1256
- kind: "annotation",
1257
- annotationKind: "description",
1258
- value: description,
1259
- provenance: provenanceForJSDocTag(descriptionTag, file)
1260
- });
1261
- }
1262
- return { constraints, annotations };
1485
+ return {
1486
+ ...displayName !== void 0 && { displayName },
1487
+ memberDisplayNames
1488
+ };
1263
1489
  }
1264
1490
  function extractPathTarget(text) {
1265
1491
  const trimmed = text.trimStart();
@@ -1323,7 +1549,45 @@ function parseConstraintValue(tagName, text, provenance) {
1323
1549
  }
1324
1550
  return null;
1325
1551
  }
1552
+ if (expectedType === "boolean") {
1553
+ const trimmed = effectiveText.trim();
1554
+ if (trimmed !== "" && trimmed !== "true") {
1555
+ return null;
1556
+ }
1557
+ if (tagName === "uniqueItems") {
1558
+ return {
1559
+ kind: "constraint",
1560
+ constraintKind: "uniqueItems",
1561
+ value: true,
1562
+ ...path3 && { path: path3 },
1563
+ provenance
1564
+ };
1565
+ }
1566
+ return null;
1567
+ }
1326
1568
  if (expectedType === "json") {
1569
+ if (tagName === "const") {
1570
+ const trimmedText = effectiveText.trim();
1571
+ if (trimmedText === "") return null;
1572
+ try {
1573
+ const parsed2 = JSON.parse(trimmedText);
1574
+ return {
1575
+ kind: "constraint",
1576
+ constraintKind: "const",
1577
+ value: parsed2,
1578
+ ...path3 && { path: path3 },
1579
+ provenance
1580
+ };
1581
+ } catch {
1582
+ return {
1583
+ kind: "constraint",
1584
+ constraintKind: "const",
1585
+ value: trimmedText,
1586
+ ...path3 && { path: path3 },
1587
+ provenance
1588
+ };
1589
+ }
1590
+ }
1327
1591
  const parsed = tryParseJson(effectiveText);
1328
1592
  if (!Array.isArray(parsed)) {
1329
1593
  return null;
@@ -1355,6 +1619,34 @@ function parseConstraintValue(tagName, text, provenance) {
1355
1619
  provenance
1356
1620
  };
1357
1621
  }
1622
+ function parseDefaultValueValue(text, provenance) {
1623
+ const trimmed = text.trim();
1624
+ let value;
1625
+ if (trimmed === "null") {
1626
+ value = null;
1627
+ } else if (trimmed === "true") {
1628
+ value = true;
1629
+ } else if (trimmed === "false") {
1630
+ value = false;
1631
+ } else {
1632
+ const parsed = tryParseJson(trimmed);
1633
+ value = parsed !== null ? parsed : trimmed;
1634
+ }
1635
+ return {
1636
+ kind: "annotation",
1637
+ annotationKind: "defaultValue",
1638
+ value,
1639
+ provenance
1640
+ };
1641
+ }
1642
+ function isMemberTargetDisplayName(text) {
1643
+ return parseMemberTargetDisplayName(text) !== null;
1644
+ }
1645
+ function parseMemberTargetDisplayName(text) {
1646
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1647
+ if (!match?.[1] || !match[2]) return null;
1648
+ return { target: match[1], label: match[2].trim() };
1649
+ }
1358
1650
  function provenanceForComment(range, sourceFile, file, tagName) {
1359
1651
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1360
1652
  return {
@@ -1436,11 +1728,17 @@ function isObjectType(type) {
1436
1728
  function isTypeReference(type) {
1437
1729
  return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1438
1730
  }
1731
+ var RESOLVING_TYPE_PLACEHOLDER = {
1732
+ kind: "object",
1733
+ properties: [],
1734
+ additionalProperties: true
1735
+ };
1439
1736
  function analyzeClassToIR(classDecl, checker, file = "") {
1440
1737
  const name = classDecl.name?.text ?? "AnonymousClass";
1441
1738
  const fields = [];
1442
1739
  const fieldLayouts = [];
1443
1740
  const typeRegistry = {};
1741
+ const annotations = extractJSDocAnnotationNodes(classDecl, file);
1444
1742
  const visiting = /* @__PURE__ */ new Set();
1445
1743
  const instanceMethods = [];
1446
1744
  const staticMethods = [];
@@ -1463,12 +1761,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1463
1761
  }
1464
1762
  }
1465
1763
  }
1466
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1764
+ return {
1765
+ name,
1766
+ fields,
1767
+ fieldLayouts,
1768
+ typeRegistry,
1769
+ ...annotations.length > 0 && { annotations },
1770
+ instanceMethods,
1771
+ staticMethods
1772
+ };
1467
1773
  }
1468
1774
  function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1469
1775
  const name = interfaceDecl.name.text;
1470
1776
  const fields = [];
1471
1777
  const typeRegistry = {};
1778
+ const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1472
1779
  const visiting = /* @__PURE__ */ new Set();
1473
1780
  for (const member of interfaceDecl.members) {
1474
1781
  if (ts4.isPropertySignature(member)) {
@@ -1479,7 +1786,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1479
1786
  }
1480
1787
  }
1481
1788
  const fieldLayouts = fields.map(() => ({}));
1482
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1789
+ return {
1790
+ name,
1791
+ fields,
1792
+ fieldLayouts,
1793
+ typeRegistry,
1794
+ ...annotations.length > 0 && { annotations },
1795
+ instanceMethods: [],
1796
+ staticMethods: []
1797
+ };
1483
1798
  }
1484
1799
  function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1485
1800
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
@@ -1494,6 +1809,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1494
1809
  const name = typeAlias.name.text;
1495
1810
  const fields = [];
1496
1811
  const typeRegistry = {};
1812
+ const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1497
1813
  const visiting = /* @__PURE__ */ new Set();
1498
1814
  for (const member of typeAlias.type.members) {
1499
1815
  if (ts4.isPropertySignature(member)) {
@@ -1510,6 +1826,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1510
1826
  fields,
1511
1827
  fieldLayouts: fields.map(() => ({})),
1512
1828
  typeRegistry,
1829
+ ...annotations.length > 0 && { annotations },
1513
1830
  instanceMethods: [],
1514
1831
  staticMethods: []
1515
1832
  }
@@ -1523,18 +1840,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1523
1840
  const tsType = checker.getTypeAtLocation(prop);
1524
1841
  const optional = prop.questionToken !== void 0;
1525
1842
  const provenance = provenanceForNode(prop, file);
1526
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1843
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1527
1844
  const constraints = [];
1528
1845
  if (prop.type) {
1529
1846
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1530
1847
  }
1531
1848
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1532
- const annotations = [];
1849
+ let annotations = [];
1533
1850
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1534
1851
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1535
- if (defaultAnnotation) {
1852
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1536
1853
  annotations.push(defaultAnnotation);
1537
1854
  }
1855
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1538
1856
  return {
1539
1857
  kind: "field",
1540
1858
  name,
@@ -1553,14 +1871,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1553
1871
  const tsType = checker.getTypeAtLocation(prop);
1554
1872
  const optional = prop.questionToken !== void 0;
1555
1873
  const provenance = provenanceForNode(prop, file);
1556
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1874
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1557
1875
  const constraints = [];
1558
1876
  if (prop.type) {
1559
1877
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1560
1878
  }
1561
1879
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1562
- const annotations = [];
1880
+ let annotations = [];
1563
1881
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1882
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1564
1883
  return {
1565
1884
  kind: "field",
1566
1885
  name,
@@ -1571,7 +1890,69 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1571
1890
  provenance
1572
1891
  };
1573
1892
  }
1574
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1893
+ function applyEnumMemberDisplayNames(type, annotations) {
1894
+ if (!annotations.some(
1895
+ (annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
1896
+ )) {
1897
+ return { type, annotations: [...annotations] };
1898
+ }
1899
+ const consumed = /* @__PURE__ */ new Set();
1900
+ const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
1901
+ if (consumed.size === 0) {
1902
+ return { type, annotations: [...annotations] };
1903
+ }
1904
+ return {
1905
+ type: nextType,
1906
+ annotations: annotations.filter((annotation) => !consumed.has(annotation))
1907
+ };
1908
+ }
1909
+ function rewriteEnumDisplayNames(type, annotations, consumed) {
1910
+ switch (type.kind) {
1911
+ case "enum":
1912
+ return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
1913
+ case "union": {
1914
+ return {
1915
+ ...type,
1916
+ members: type.members.map(
1917
+ (member) => rewriteEnumDisplayNames(member, annotations, consumed)
1918
+ )
1919
+ };
1920
+ }
1921
+ default:
1922
+ return type;
1923
+ }
1924
+ }
1925
+ function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
1926
+ const displayNames = /* @__PURE__ */ new Map();
1927
+ for (const annotation of annotations) {
1928
+ if (annotation.annotationKind !== "displayName") continue;
1929
+ const parsed = parseEnumMemberDisplayName(annotation.value);
1930
+ if (!parsed) continue;
1931
+ consumed.add(annotation);
1932
+ const member = type.members.find((m) => String(m.value) === parsed.value);
1933
+ if (!member) continue;
1934
+ displayNames.set(String(member.value), parsed.label);
1935
+ }
1936
+ if (displayNames.size === 0) {
1937
+ return type;
1938
+ }
1939
+ return {
1940
+ ...type,
1941
+ members: type.members.map((member) => {
1942
+ const displayName = displayNames.get(String(member.value));
1943
+ return displayName !== void 0 ? { ...member, displayName } : member;
1944
+ })
1945
+ };
1946
+ }
1947
+ function parseEnumMemberDisplayName(value) {
1948
+ const trimmed = value.trim();
1949
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
1950
+ if (!match?.[1] || !match[2]) return null;
1951
+ const label = match[2].trim();
1952
+ if (label === "") return null;
1953
+ return { value: match[1], label };
1954
+ }
1955
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1575
1956
  if (type.flags & ts4.TypeFlags.String) {
1576
1957
  return { kind: "primitive", primitiveKind: "string" };
1577
1958
  }
@@ -1600,7 +1981,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1600
1981
  };
1601
1982
  }
1602
1983
  if (type.isUnion()) {
1603
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
1984
+ return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1604
1985
  }
1605
1986
  if (checker.isArrayType(type)) {
1606
1987
  return resolveArrayType(type, checker, file, typeRegistry, visiting);
@@ -1610,70 +1991,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1610
1991
  }
1611
1992
  return { kind: "primitive", primitiveKind: "string" };
1612
1993
  }
1613
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
1994
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
1995
+ const typeName = getNamedTypeName(type);
1996
+ const namedDecl = getNamedTypeDeclaration(type);
1997
+ if (typeName && typeName in typeRegistry) {
1998
+ return { kind: "reference", name: typeName, typeArguments: [] };
1999
+ }
1614
2000
  const allTypes = type.types;
1615
2001
  const nonNullTypes = allTypes.filter(
1616
2002
  (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1617
2003
  );
1618
2004
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2005
+ const memberDisplayNames = /* @__PURE__ */ new Map();
2006
+ if (namedDecl) {
2007
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
2008
+ memberDisplayNames.set(value, label);
2009
+ }
2010
+ }
2011
+ if (sourceNode) {
2012
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
2013
+ memberDisplayNames.set(value, label);
2014
+ }
2015
+ }
2016
+ const registerNamed = (result) => {
2017
+ if (!typeName) {
2018
+ return result;
2019
+ }
2020
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2021
+ typeRegistry[typeName] = {
2022
+ name: typeName,
2023
+ type: result,
2024
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2025
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
2026
+ };
2027
+ return { kind: "reference", name: typeName, typeArguments: [] };
2028
+ };
2029
+ const applyMemberLabels = (members2) => members2.map((value) => {
2030
+ const displayName = memberDisplayNames.get(String(value));
2031
+ return displayName !== void 0 ? { value, displayName } : { value };
2032
+ });
1619
2033
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1620
2034
  if (isBooleanUnion2) {
1621
2035
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1622
- if (hasNull) {
1623
- return {
1624
- kind: "union",
1625
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1626
- };
1627
- }
1628
- return boolNode;
2036
+ const result = hasNull ? {
2037
+ kind: "union",
2038
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
2039
+ } : boolNode;
2040
+ return registerNamed(result);
1629
2041
  }
1630
2042
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1631
2043
  if (allStringLiterals && nonNullTypes.length > 0) {
1632
2044
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1633
2045
  const enumNode = {
1634
2046
  kind: "enum",
1635
- members: stringTypes.map((t) => ({ value: t.value }))
2047
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1636
2048
  };
1637
- if (hasNull) {
1638
- return {
1639
- kind: "union",
1640
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1641
- };
1642
- }
1643
- return enumNode;
2049
+ const result = hasNull ? {
2050
+ kind: "union",
2051
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2052
+ } : enumNode;
2053
+ return registerNamed(result);
1644
2054
  }
1645
2055
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1646
2056
  if (allNumberLiterals && nonNullTypes.length > 0) {
1647
2057
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1648
2058
  const enumNode = {
1649
2059
  kind: "enum",
1650
- members: numberTypes.map((t) => ({ value: t.value }))
2060
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1651
2061
  };
1652
- if (hasNull) {
1653
- return {
1654
- kind: "union",
1655
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1656
- };
1657
- }
1658
- return enumNode;
2062
+ const result = hasNull ? {
2063
+ kind: "union",
2064
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2065
+ } : enumNode;
2066
+ return registerNamed(result);
1659
2067
  }
1660
2068
  if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1661
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1662
- if (hasNull) {
1663
- return {
1664
- kind: "union",
1665
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1666
- };
1667
- }
1668
- return inner;
2069
+ const inner = resolveTypeNode(
2070
+ nonNullTypes[0],
2071
+ checker,
2072
+ file,
2073
+ typeRegistry,
2074
+ visiting,
2075
+ sourceNode
2076
+ );
2077
+ const result = hasNull ? {
2078
+ kind: "union",
2079
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2080
+ } : inner;
2081
+ return registerNamed(result);
1669
2082
  }
1670
2083
  const members = nonNullTypes.map(
1671
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2084
+ (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1672
2085
  );
1673
2086
  if (hasNull) {
1674
2087
  members.push({ kind: "primitive", primitiveKind: "null" });
1675
2088
  }
1676
- return { kind: "union", members };
2089
+ return registerNamed({ kind: "union", members });
1677
2090
  }
1678
2091
  function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1679
2092
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
@@ -1681,15 +2094,92 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1681
2094
  const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1682
2095
  return { kind: "array", items };
1683
2096
  }
2097
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2098
+ if (type.getProperties().length > 0) {
2099
+ return null;
2100
+ }
2101
+ const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
2102
+ if (!indexInfo) {
2103
+ return null;
2104
+ }
2105
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2106
+ return { kind: "record", valueType };
2107
+ }
2108
+ function typeNodeContainsReference(type, targetName) {
2109
+ switch (type.kind) {
2110
+ case "reference":
2111
+ return type.name === targetName;
2112
+ case "array":
2113
+ return typeNodeContainsReference(type.items, targetName);
2114
+ case "record":
2115
+ return typeNodeContainsReference(type.valueType, targetName);
2116
+ case "union":
2117
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2118
+ case "object":
2119
+ return type.properties.some(
2120
+ (property) => typeNodeContainsReference(property.type, targetName)
2121
+ );
2122
+ case "primitive":
2123
+ case "enum":
2124
+ case "dynamic":
2125
+ case "custom":
2126
+ return false;
2127
+ default: {
2128
+ const _exhaustive = type;
2129
+ return _exhaustive;
2130
+ }
2131
+ }
2132
+ }
1684
2133
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2134
+ const typeName = getNamedTypeName(type);
2135
+ const namedTypeName = typeName ?? void 0;
2136
+ const namedDecl = getNamedTypeDeclaration(type);
2137
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2138
+ const clearNamedTypeRegistration = () => {
2139
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2140
+ return;
2141
+ }
2142
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2143
+ };
1685
2144
  if (visiting.has(type)) {
2145
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2146
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2147
+ }
1686
2148
  return { kind: "object", properties: [], additionalProperties: false };
1687
2149
  }
2150
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2151
+ typeRegistry[namedTypeName] = {
2152
+ name: namedTypeName,
2153
+ type: RESOLVING_TYPE_PLACEHOLDER,
2154
+ provenance: provenanceForDeclaration(namedDecl, file)
2155
+ };
2156
+ }
1688
2157
  visiting.add(type);
1689
- const typeName = getNamedTypeName(type);
1690
- if (typeName && typeName in typeRegistry) {
2158
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2159
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2160
+ visiting.delete(type);
2161
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2162
+ }
2163
+ }
2164
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2165
+ if (recordNode) {
1691
2166
  visiting.delete(type);
1692
- return { kind: "reference", name: typeName, typeArguments: [] };
2167
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2168
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2169
+ if (!isRecursiveRecord) {
2170
+ clearNamedTypeRegistration();
2171
+ return recordNode;
2172
+ }
2173
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2174
+ typeRegistry[namedTypeName] = {
2175
+ name: namedTypeName,
2176
+ type: recordNode,
2177
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2178
+ provenance: provenanceForDeclaration(namedDecl, file)
2179
+ };
2180
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2181
+ }
2182
+ return recordNode;
1693
2183
  }
1694
2184
  const properties = [];
1695
2185
  const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
@@ -1698,7 +2188,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1698
2188
  if (!declaration) continue;
1699
2189
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1700
2190
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1701
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2191
+ const propTypeNode = resolveTypeNode(
2192
+ propType,
2193
+ checker,
2194
+ file,
2195
+ typeRegistry,
2196
+ visiting,
2197
+ declaration
2198
+ );
1702
2199
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1703
2200
  properties.push({
1704
2201
  name: prop.name,
@@ -1713,15 +2210,17 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1713
2210
  const objectNode = {
1714
2211
  kind: "object",
1715
2212
  properties,
1716
- additionalProperties: false
2213
+ additionalProperties: true
1717
2214
  };
1718
- if (typeName) {
1719
- typeRegistry[typeName] = {
1720
- name: typeName,
2215
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2216
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2217
+ typeRegistry[namedTypeName] = {
2218
+ name: namedTypeName,
1721
2219
  type: objectNode,
1722
- provenance: provenanceForFile(file)
2220
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2221
+ provenance: provenanceForDeclaration(namedDecl, file)
1723
2222
  };
1724
- return { kind: "reference", name: typeName, typeArguments: [] };
2223
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1725
2224
  }
1726
2225
  return objectNode;
1727
2226
  }
@@ -1813,6 +2312,12 @@ function provenanceForNode(node, file) {
1813
2312
  function provenanceForFile(file) {
1814
2313
  return { surface: "tsdoc", file, line: 0, column: 0 };
1815
2314
  }
2315
+ function provenanceForDeclaration(node, file) {
2316
+ if (!node) {
2317
+ return provenanceForFile(file);
2318
+ }
2319
+ return provenanceForNode(node, file);
2320
+ }
1816
2321
  function getNamedTypeName(type) {
1817
2322
  const symbol = type.getSymbol();
1818
2323
  if (symbol?.declarations) {
@@ -1831,6 +2336,20 @@ function getNamedTypeName(type) {
1831
2336
  }
1832
2337
  return null;
1833
2338
  }
2339
+ function getNamedTypeDeclaration(type) {
2340
+ const symbol = type.getSymbol();
2341
+ if (symbol?.declarations) {
2342
+ const decl = symbol.declarations[0];
2343
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2344
+ return decl;
2345
+ }
2346
+ }
2347
+ const aliasSymbol = type.aliasSymbol;
2348
+ if (aliasSymbol?.declarations) {
2349
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2350
+ }
2351
+ return void 0;
2352
+ }
1834
2353
  function analyzeMethod(method, checker) {
1835
2354
  if (!ts4.isIdentifier(method.name)) {
1836
2355
  return null;
@@ -1915,16 +2434,219 @@ function generateSchemas(options) {
1915
2434
  );
1916
2435
  }
1917
2436
 
2437
+ // src/generators/mixed-authoring.ts
2438
+ function buildMixedAuthoringSchemas(options) {
2439
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2440
+ const analysis = analyzeNamedType(filePath, typeName);
2441
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2442
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2443
+ return {
2444
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2445
+ uiSchema: generateUiSchemaFromIR(ir)
2446
+ };
2447
+ }
2448
+ function analyzeNamedType(filePath, typeName) {
2449
+ const ctx = createProgramContext(filePath);
2450
+ const source = { file: filePath };
2451
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2452
+ if (classDecl !== null) {
2453
+ return analyzeClassToIR(classDecl, ctx.checker, source.file);
2454
+ }
2455
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2456
+ if (interfaceDecl !== null) {
2457
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2458
+ }
2459
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2460
+ if (typeAlias !== null) {
2461
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2462
+ if (result.ok) {
2463
+ return result.analysis;
2464
+ }
2465
+ throw new Error(result.error);
2466
+ }
2467
+ throw new Error(
2468
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2469
+ );
2470
+ }
2471
+ function composeAnalysisWithOverlays(analysis, overlays) {
2472
+ const overlayIR = canonicalizeChainDSL(overlays);
2473
+ const overlayFields = collectOverlayFields(overlayIR.elements);
2474
+ if (overlayFields.length === 0) {
2475
+ return analysis;
2476
+ }
2477
+ const overlayByName = /* @__PURE__ */ new Map();
2478
+ for (const field of overlayFields) {
2479
+ if (overlayByName.has(field.name)) {
2480
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
2481
+ }
2482
+ overlayByName.set(field.name, field);
2483
+ }
2484
+ const mergedFields = [];
2485
+ for (const baseField of analysis.fields) {
2486
+ const overlayField = overlayByName.get(baseField.name);
2487
+ if (overlayField === void 0) {
2488
+ mergedFields.push(baseField);
2489
+ continue;
2490
+ }
2491
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
2492
+ overlayByName.delete(baseField.name);
2493
+ }
2494
+ if (overlayByName.size > 0) {
2495
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
2496
+ throw new Error(
2497
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
2498
+ );
2499
+ }
2500
+ return {
2501
+ ...analysis,
2502
+ fields: mergedFields
2503
+ };
2504
+ }
2505
+ function collectOverlayFields(elements) {
2506
+ const fields = [];
2507
+ for (const element of elements) {
2508
+ switch (element.kind) {
2509
+ case "field":
2510
+ fields.push(element);
2511
+ break;
2512
+ case "group":
2513
+ fields.push(...collectOverlayFields(element.elements));
2514
+ break;
2515
+ case "conditional":
2516
+ fields.push(...collectOverlayFields(element.elements));
2517
+ break;
2518
+ default: {
2519
+ const _exhaustive = element;
2520
+ void _exhaustive;
2521
+ }
2522
+ }
2523
+ }
2524
+ return fields;
2525
+ }
2526
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
2527
+ assertSupportedOverlayField(baseField, overlayField);
2528
+ return {
2529
+ ...baseField,
2530
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
2531
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
2532
+ };
2533
+ }
2534
+ function assertSupportedOverlayField(baseField, overlayField) {
2535
+ if (overlayField.constraints.length > 0) {
2536
+ throw new Error(
2537
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2538
+ );
2539
+ }
2540
+ if (overlayField.required) {
2541
+ throw new Error(
2542
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2543
+ );
2544
+ }
2545
+ }
2546
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
2547
+ const { type: baseType } = baseField;
2548
+ const { type: overlayType } = overlayField;
2549
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
2550
+ throw new Error(
2551
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
2552
+ );
2553
+ }
2554
+ if (overlayType.kind === "dynamic") {
2555
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
2556
+ throw new Error(
2557
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
2558
+ );
2559
+ }
2560
+ return overlayType;
2561
+ }
2562
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
2563
+ throw new Error(
2564
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
2565
+ );
2566
+ }
2567
+ return baseType;
2568
+ }
2569
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
2570
+ const overlayType = overlayField.type;
2571
+ if (overlayType.kind !== "dynamic") {
2572
+ return false;
2573
+ }
2574
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
2575
+ if (resolvedBaseType === null) {
2576
+ return false;
2577
+ }
2578
+ if (overlayType.dynamicKind === "enum") {
2579
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
2580
+ }
2581
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
2582
+ }
2583
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
2584
+ if (type.kind !== "reference") {
2585
+ return type;
2586
+ }
2587
+ if (seen.has(type.name)) {
2588
+ return null;
2589
+ }
2590
+ const definition = typeRegistry[type.name];
2591
+ if (definition === void 0) {
2592
+ return null;
2593
+ }
2594
+ seen.add(type.name);
2595
+ return resolveReferenceType(definition.type, typeRegistry, seen);
2596
+ }
2597
+ function isSameStaticTypeShape(baseType, overlayType) {
2598
+ if (baseType.kind !== overlayType.kind) {
2599
+ return false;
2600
+ }
2601
+ switch (baseType.kind) {
2602
+ case "primitive":
2603
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
2604
+ case "enum":
2605
+ return overlayType.kind === "enum";
2606
+ case "dynamic":
2607
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
2608
+ case "record":
2609
+ return overlayType.kind === "record";
2610
+ case "reference":
2611
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
2612
+ case "union":
2613
+ return overlayType.kind === "union";
2614
+ case "custom":
2615
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
2616
+ case "object":
2617
+ case "array":
2618
+ return true;
2619
+ default: {
2620
+ const _exhaustive = baseType;
2621
+ return _exhaustive;
2622
+ }
2623
+ }
2624
+ }
2625
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2626
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
2627
+ const overlayOnly = overlayAnnotations.filter(
2628
+ (annotation) => !baseKeys.has(annotationKey(annotation))
2629
+ );
2630
+ return [...overlayOnly, ...baseAnnotations];
2631
+ }
2632
+ function annotationKey(annotation) {
2633
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
2634
+ }
2635
+
1918
2636
  // src/index.ts
1919
- function buildFormSchemas(form) {
2637
+ function buildFormSchemas(form, options) {
1920
2638
  return {
1921
- jsonSchema: generateJsonSchema(form),
2639
+ jsonSchema: generateJsonSchema(form, options),
1922
2640
  uiSchema: generateUiSchema(form)
1923
2641
  };
1924
2642
  }
1925
2643
  function writeSchemas(form, options) {
1926
- const { outDir, name = "schema", indent = 2 } = options;
1927
- const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
2644
+ const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
2645
+ const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
2646
+ extensionRegistry,
2647
+ vendorPrefix
2648
+ };
2649
+ const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
1928
2650
  if (!fs.existsSync(outDir)) {
1929
2651
  fs.mkdirSync(outDir, { recursive: true });
1930
2652
  }
@@ -1937,10 +2659,13 @@ function writeSchemas(form, options) {
1937
2659
  // Annotate the CommonJS export names for ESM import in node:
1938
2660
  0 && (module.exports = {
1939
2661
  buildFormSchemas,
2662
+ buildMixedAuthoringSchemas,
1940
2663
  categorizationSchema,
1941
2664
  categorySchema,
1942
2665
  controlSchema,
2666
+ createExtensionRegistry,
1943
2667
  generateJsonSchema,
2668
+ generateJsonSchemaFromIR,
1944
2669
  generateSchemas,
1945
2670
  generateSchemasFromClass,
1946
2671
  generateUiSchema,