@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.cjs CHANGED
@@ -202,7 +202,7 @@ function canonicalizeArrayField(field) {
202
202
  const itemsType = {
203
203
  kind: "object",
204
204
  properties: itemProperties,
205
- additionalProperties: false
205
+ additionalProperties: true
206
206
  };
207
207
  const type = { kind: "array", items: itemsType };
208
208
  const constraints = [];
@@ -237,7 +237,7 @@ function canonicalizeObjectField(field) {
237
237
  const type = {
238
238
  kind: "object",
239
239
  properties,
240
- additionalProperties: false
240
+ additionalProperties: true
241
241
  };
242
242
  return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
243
243
  }
@@ -362,6 +362,7 @@ function canonicalizeTSDoc(analysis, source) {
362
362
  irVersion: import_core2.IR_VERSION,
363
363
  elements,
364
364
  typeRegistry: analysis.typeRegistry,
365
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
365
366
  provenance
366
367
  };
367
368
  }
@@ -437,13 +438,26 @@ var init_canonicalize = __esm({
437
438
  });
438
439
 
439
440
  // src/json-schema/ir-generator.ts
440
- function makeContext() {
441
- return { defs: {} };
441
+ function makeContext(options) {
442
+ const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
443
+ if (!vendorPrefix.startsWith("x-")) {
444
+ throw new Error(
445
+ `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
446
+ );
447
+ }
448
+ return {
449
+ defs: {},
450
+ extensionRegistry: options?.extensionRegistry,
451
+ vendorPrefix
452
+ };
442
453
  }
443
- function generateJsonSchemaFromIR(ir) {
444
- const ctx = makeContext();
454
+ function generateJsonSchemaFromIR(ir, options) {
455
+ const ctx = makeContext(options);
445
456
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
446
457
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
458
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
459
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
460
+ }
447
461
  }
448
462
  const properties = {};
449
463
  const required = [];
@@ -455,6 +469,9 @@ function generateJsonSchemaFromIR(ir) {
455
469
  properties,
456
470
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
457
471
  };
472
+ if (ir.annotations && ir.annotations.length > 0) {
473
+ applyAnnotations(result, ir.annotations, ctx);
474
+ }
458
475
  if (Object.keys(ctx.defs).length > 0) {
459
476
  result.$defs = ctx.defs;
460
477
  }
@@ -484,25 +501,54 @@ function collectFields(elements, properties, required, ctx) {
484
501
  }
485
502
  function generateFieldSchema(field, ctx) {
486
503
  const schema = generateTypeNode(field.type, ctx);
504
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
487
505
  const directConstraints = [];
506
+ const itemConstraints = [];
488
507
  const pathConstraints = [];
489
508
  for (const c of field.constraints) {
490
509
  if (c.path) {
491
510
  pathConstraints.push(c);
511
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
512
+ itemConstraints.push(c);
492
513
  } else {
493
514
  directConstraints.push(c);
494
515
  }
495
516
  }
496
- applyConstraints(schema, directConstraints);
497
- applyAnnotations(schema, field.annotations);
517
+ applyConstraints(schema, directConstraints, ctx);
518
+ if (itemStringSchema !== void 0) {
519
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
520
+ }
521
+ const rootAnnotations = [];
522
+ const itemAnnotations = [];
523
+ for (const annotation of field.annotations) {
524
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
525
+ itemAnnotations.push(annotation);
526
+ } else {
527
+ rootAnnotations.push(annotation);
528
+ }
529
+ }
530
+ applyAnnotations(schema, rootAnnotations, ctx);
531
+ if (itemStringSchema !== void 0) {
532
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
533
+ }
498
534
  if (pathConstraints.length === 0) {
499
535
  return schema;
500
536
  }
501
- return applyPathTargetedConstraints(schema, pathConstraints);
537
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx);
502
538
  }
503
- function applyPathTargetedConstraints(schema, pathConstraints) {
539
+ function isStringItemConstraint(constraint) {
540
+ switch (constraint.constraintKind) {
541
+ case "minLength":
542
+ case "maxLength":
543
+ case "pattern":
544
+ return true;
545
+ default:
546
+ return false;
547
+ }
548
+ }
549
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
504
550
  if (schema.type === "array" && schema.items) {
505
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
551
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
506
552
  return schema;
507
553
  }
508
554
  const byTarget = /* @__PURE__ */ new Map();
@@ -516,7 +562,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
516
562
  const propertyOverrides = {};
517
563
  for (const [target, constraints] of byTarget) {
518
564
  const subSchema = {};
519
- applyConstraints(subSchema, constraints);
565
+ applyConstraints(subSchema, constraints, ctx);
520
566
  propertyOverrides[target] = subSchema;
521
567
  }
522
568
  if (schema.$ref) {
@@ -560,6 +606,8 @@ function generateTypeNode(type, ctx) {
560
606
  return generateArrayType(type, ctx);
561
607
  case "object":
562
608
  return generateObjectType(type, ctx);
609
+ case "record":
610
+ return generateRecordType(type, ctx);
563
611
  case "union":
564
612
  return generateUnionType(type, ctx);
565
613
  case "reference":
@@ -567,7 +615,7 @@ function generateTypeNode(type, ctx) {
567
615
  case "dynamic":
568
616
  return generateDynamicType(type);
569
617
  case "custom":
570
- return generateCustomType(type);
618
+ return generateCustomType(type, ctx);
571
619
  default: {
572
620
  const _exhaustive = type;
573
621
  return _exhaustive;
@@ -616,16 +664,27 @@ function generateObjectType(type, ctx) {
616
664
  }
617
665
  return schema;
618
666
  }
667
+ function generateRecordType(type, ctx) {
668
+ return {
669
+ type: "object",
670
+ additionalProperties: generateTypeNode(type.valueType, ctx)
671
+ };
672
+ }
619
673
  function generatePropertySchema(prop, ctx) {
620
674
  const schema = generateTypeNode(prop.type, ctx);
621
- applyConstraints(schema, prop.constraints);
622
- applyAnnotations(schema, prop.annotations);
675
+ applyConstraints(schema, prop.constraints, ctx);
676
+ applyAnnotations(schema, prop.annotations, ctx);
623
677
  return schema;
624
678
  }
625
679
  function generateUnionType(type, ctx) {
626
680
  if (isBooleanUnion(type)) {
627
681
  return { type: "boolean" };
628
682
  }
683
+ if (isNullableUnion(type)) {
684
+ return {
685
+ oneOf: type.members.map((m) => generateTypeNode(m, ctx))
686
+ };
687
+ }
629
688
  return {
630
689
  anyOf: type.members.map((m) => generateTypeNode(m, ctx))
631
690
  };
@@ -635,6 +694,13 @@ function isBooleanUnion(type) {
635
694
  const kinds = type.members.map((m) => m.kind);
636
695
  return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
637
696
  }
697
+ function isNullableUnion(type) {
698
+ if (type.members.length !== 2) return false;
699
+ const nullCount = type.members.filter(
700
+ (m) => m.kind === "primitive" && m.primitiveKind === "null"
701
+ ).length;
702
+ return nullCount === 1;
703
+ }
638
704
  function generateReferenceType(type) {
639
705
  return { $ref: `#/$defs/${type.name}` };
640
706
  }
@@ -655,10 +721,7 @@ function generateDynamicType(type) {
655
721
  "x-formspec-schemaSource": type.sourceKey
656
722
  };
657
723
  }
658
- function generateCustomType(_type) {
659
- return { type: "object" };
660
- }
661
- function applyConstraints(schema, constraints) {
724
+ function applyConstraints(schema, constraints, ctx) {
662
725
  for (const constraint of constraints) {
663
726
  switch (constraint.constraintKind) {
664
727
  case "minimum":
@@ -700,9 +763,13 @@ function applyConstraints(schema, constraints) {
700
763
  case "uniqueItems":
701
764
  schema.uniqueItems = constraint.value;
702
765
  break;
766
+ case "const":
767
+ schema.const = constraint.value;
768
+ break;
703
769
  case "allowedMembers":
704
770
  break;
705
771
  case "custom":
772
+ applyCustomConstraint(schema, constraint, ctx);
706
773
  break;
707
774
  default: {
708
775
  const _exhaustive = constraint;
@@ -711,7 +778,7 @@ function applyConstraints(schema, constraints) {
711
778
  }
712
779
  }
713
780
  }
714
- function applyAnnotations(schema, annotations) {
781
+ function applyAnnotations(schema, annotations, ctx) {
715
782
  for (const annotation of annotations) {
716
783
  switch (annotation.annotationKind) {
717
784
  case "displayName":
@@ -723,14 +790,21 @@ function applyAnnotations(schema, annotations) {
723
790
  case "defaultValue":
724
791
  schema.default = annotation.value;
725
792
  break;
793
+ case "format":
794
+ schema.format = annotation.value;
795
+ break;
726
796
  case "deprecated":
727
797
  schema.deprecated = true;
798
+ if (annotation.message !== void 0 && annotation.message !== "") {
799
+ schema["x-formspec-deprecation-description"] = annotation.message;
800
+ }
728
801
  break;
729
802
  case "placeholder":
730
803
  break;
731
804
  case "formatHint":
732
805
  break;
733
806
  case "custom":
807
+ applyCustomAnnotation(schema, annotation, ctx);
734
808
  break;
735
809
  default: {
736
810
  const _exhaustive = annotation;
@@ -739,6 +813,36 @@ function applyAnnotations(schema, annotations) {
739
813
  }
740
814
  }
741
815
  }
816
+ function generateCustomType(type, ctx) {
817
+ const registration = ctx.extensionRegistry?.findType(type.typeId);
818
+ if (registration === void 0) {
819
+ throw new Error(
820
+ `Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
821
+ );
822
+ }
823
+ return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
824
+ }
825
+ function applyCustomConstraint(schema, constraint, ctx) {
826
+ const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
827
+ if (registration === void 0) {
828
+ throw new Error(
829
+ `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
830
+ );
831
+ }
832
+ Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
833
+ }
834
+ function applyCustomAnnotation(schema, annotation, ctx) {
835
+ const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
836
+ if (registration === void 0) {
837
+ throw new Error(
838
+ `Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
839
+ );
840
+ }
841
+ if (registration.toJsonSchema === void 0) {
842
+ return;
843
+ }
844
+ Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
845
+ }
742
846
  var init_ir_generator = __esm({
743
847
  "src/json-schema/ir-generator.ts"() {
744
848
  "use strict";
@@ -746,9 +850,9 @@ var init_ir_generator = __esm({
746
850
  });
747
851
 
748
852
  // src/json-schema/generator.ts
749
- function generateJsonSchema(form) {
853
+ function generateJsonSchema(form, options) {
750
854
  const ir = canonicalizeChainDSL(form);
751
- return generateJsonSchemaFromIR(ir);
855
+ return generateJsonSchemaFromIR(ir, options);
752
856
  }
753
857
  var init_generator = __esm({
754
858
  "src/json-schema/generator.ts"() {
@@ -898,25 +1002,31 @@ function createShowRule(fieldName, value) {
898
1002
  }
899
1003
  };
900
1004
  }
1005
+ function flattenConditionSchema(scope, schema) {
1006
+ if (schema.allOf === void 0) {
1007
+ if (scope === "#") {
1008
+ return [schema];
1009
+ }
1010
+ const fieldName = scope.replace("#/properties/", "");
1011
+ return [
1012
+ {
1013
+ properties: {
1014
+ [fieldName]: schema
1015
+ }
1016
+ }
1017
+ ];
1018
+ }
1019
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
1020
+ }
901
1021
  function combineRules(parentRule, childRule) {
902
- const parentCondition = parentRule.condition;
903
- const childCondition = childRule.condition;
904
1022
  return {
905
1023
  effect: "SHOW",
906
1024
  condition: {
907
1025
  scope: "#",
908
1026
  schema: {
909
1027
  allOf: [
910
- {
911
- properties: {
912
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
913
- }
914
- },
915
- {
916
- properties: {
917
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
918
- }
919
- }
1028
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
1029
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
920
1030
  ]
921
1031
  }
922
1032
  }
@@ -924,10 +1034,14 @@ function combineRules(parentRule, childRule) {
924
1034
  }
925
1035
  function fieldNodeToControl(field, parentRule) {
926
1036
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1037
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
927
1038
  const control = {
928
1039
  type: "Control",
929
1040
  scope: fieldToScope(field.name),
930
1041
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1042
+ ...placeholderAnnotation !== void 0 && {
1043
+ options: { placeholder: placeholderAnnotation.value }
1044
+ },
931
1045
  ...parentRule !== void 0 && { rule: parentRule }
932
1046
  };
933
1047
  return control;
@@ -1010,6 +1124,61 @@ var init_types = __esm({
1010
1124
  }
1011
1125
  });
1012
1126
 
1127
+ // src/extensions/registry.ts
1128
+ function createExtensionRegistry(extensions) {
1129
+ const typeMap = /* @__PURE__ */ new Map();
1130
+ const constraintMap = /* @__PURE__ */ new Map();
1131
+ const annotationMap = /* @__PURE__ */ new Map();
1132
+ for (const ext of extensions) {
1133
+ if (ext.types !== void 0) {
1134
+ for (const type of ext.types) {
1135
+ const qualifiedId = `${ext.extensionId}/${type.typeName}`;
1136
+ if (typeMap.has(qualifiedId)) {
1137
+ throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1138
+ }
1139
+ typeMap.set(qualifiedId, type);
1140
+ }
1141
+ }
1142
+ if (ext.constraints !== void 0) {
1143
+ for (const constraint of ext.constraints) {
1144
+ const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
1145
+ if (constraintMap.has(qualifiedId)) {
1146
+ throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
1147
+ }
1148
+ constraintMap.set(qualifiedId, constraint);
1149
+ }
1150
+ }
1151
+ if (ext.annotations !== void 0) {
1152
+ for (const annotation of ext.annotations) {
1153
+ const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
1154
+ if (annotationMap.has(qualifiedId)) {
1155
+ throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
1156
+ }
1157
+ annotationMap.set(qualifiedId, annotation);
1158
+ }
1159
+ }
1160
+ }
1161
+ return {
1162
+ extensions,
1163
+ findType: (typeId) => typeMap.get(typeId),
1164
+ findConstraint: (constraintId) => constraintMap.get(constraintId),
1165
+ findAnnotation: (annotationId) => annotationMap.get(annotationId)
1166
+ };
1167
+ }
1168
+ var init_registry = __esm({
1169
+ "src/extensions/registry.ts"() {
1170
+ "use strict";
1171
+ }
1172
+ });
1173
+
1174
+ // src/extensions/index.ts
1175
+ var init_extensions = __esm({
1176
+ "src/extensions/index.ts"() {
1177
+ "use strict";
1178
+ init_registry();
1179
+ }
1180
+ });
1181
+
1013
1182
  // src/json-schema/schema.ts
1014
1183
  var import_zod3, jsonSchemaTypeSchema, jsonSchema7Schema;
1015
1184
  var init_schema2 = __esm({
@@ -1182,6 +1351,15 @@ function createFormSpecTSDocConfig() {
1182
1351
  })
1183
1352
  );
1184
1353
  }
1354
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1355
+ config.addTagDefinition(
1356
+ new import_tsdoc.TSDocTagDefinition({
1357
+ tagName: "@" + tagName,
1358
+ syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
1359
+ allowMultiple: true
1360
+ })
1361
+ );
1362
+ }
1185
1363
  return config;
1186
1364
  }
1187
1365
  function getParser() {
@@ -1191,6 +1369,12 @@ function getParser() {
1191
1369
  function parseTSDocTags(node, file = "") {
1192
1370
  const constraints = [];
1193
1371
  const annotations = [];
1372
+ let displayName;
1373
+ let description;
1374
+ let placeholder;
1375
+ let displayNameProvenance;
1376
+ let descriptionProvenance;
1377
+ let placeholderProvenance;
1194
1378
  const sourceFile = node.getSourceFile();
1195
1379
  const sourceText = sourceFile.getFullText();
1196
1380
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1210,9 +1394,37 @@ function parseTSDocTags(node, file = "") {
1210
1394
  const docComment = parserContext.docComment;
1211
1395
  for (const block of docComment.customBlocks) {
1212
1396
  const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
1397
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1398
+ const text2 = extractBlockText(block).trim();
1399
+ if (text2 === "") continue;
1400
+ const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1401
+ if (tagName === "displayName") {
1402
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1403
+ displayName = text2;
1404
+ displayNameProvenance = provenance2;
1405
+ }
1406
+ } else if (tagName === "format") {
1407
+ annotations.push({
1408
+ kind: "annotation",
1409
+ annotationKind: "format",
1410
+ value: text2,
1411
+ provenance: provenance2
1412
+ });
1413
+ } else {
1414
+ if (tagName === "description" && description === void 0) {
1415
+ description = text2;
1416
+ descriptionProvenance = provenance2;
1417
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1418
+ placeholder = text2;
1419
+ placeholderProvenance = provenance2;
1420
+ }
1421
+ }
1422
+ continue;
1423
+ }
1213
1424
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1214
1425
  const text = extractBlockText(block).trim();
1215
- if (text === "") continue;
1426
+ const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1427
+ if (text === "" && expectedType !== "boolean") continue;
1216
1428
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1217
1429
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1218
1430
  if (constraintNode) {
@@ -1220,14 +1432,47 @@ function parseTSDocTags(node, file = "") {
1220
1432
  }
1221
1433
  }
1222
1434
  if (docComment.deprecatedBlock !== void 0) {
1435
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1223
1436
  annotations.push({
1224
1437
  kind: "annotation",
1225
1438
  annotationKind: "deprecated",
1439
+ ...message !== "" && { message },
1226
1440
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1227
1441
  });
1228
1442
  }
1443
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1444
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1445
+ if (remarks !== "") {
1446
+ description = remarks;
1447
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1448
+ }
1449
+ }
1229
1450
  }
1230
1451
  }
1452
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1453
+ annotations.push({
1454
+ kind: "annotation",
1455
+ annotationKind: "displayName",
1456
+ value: displayName,
1457
+ provenance: displayNameProvenance
1458
+ });
1459
+ }
1460
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1461
+ annotations.push({
1462
+ kind: "annotation",
1463
+ annotationKind: "description",
1464
+ value: description,
1465
+ provenance: descriptionProvenance
1466
+ });
1467
+ }
1468
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1469
+ annotations.push({
1470
+ kind: "annotation",
1471
+ annotationKind: "placeholder",
1472
+ value: placeholder,
1473
+ provenance: placeholderProvenance
1474
+ });
1475
+ }
1231
1476
  const jsDocTagsAll = ts2.getJSDocTags(node);
1232
1477
  for (const tag of jsDocTagsAll) {
1233
1478
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
@@ -1236,47 +1481,39 @@ function parseTSDocTags(node, file = "") {
1236
1481
  if (commentText === void 0 || commentText.trim() === "") continue;
1237
1482
  const text = commentText.trim();
1238
1483
  const provenance = provenanceForJSDocTag(tag, file);
1484
+ if (tagName === "defaultValue") {
1485
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1486
+ annotations.push(defaultValueNode);
1487
+ continue;
1488
+ }
1239
1489
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1240
1490
  if (constraintNode) {
1241
1491
  constraints.push(constraintNode);
1242
1492
  }
1243
1493
  }
1494
+ return { constraints, annotations };
1495
+ }
1496
+ function extractDisplayNameMetadata(node) {
1244
1497
  let displayName;
1245
- let description;
1246
- let displayNameTag;
1247
- let descriptionTag;
1248
- for (const tag of jsDocTagsAll) {
1249
- const tagName = tag.tagName.text;
1498
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1499
+ for (const tag of ts2.getJSDocTags(node)) {
1500
+ const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1501
+ if (tagName !== "displayName") continue;
1250
1502
  const commentText = getTagCommentText(tag);
1251
- if (commentText === void 0 || commentText.trim() === "") {
1503
+ if (commentText === void 0) continue;
1504
+ const text = commentText.trim();
1505
+ if (text === "") continue;
1506
+ const memberTarget = parseMemberTargetDisplayName(text);
1507
+ if (memberTarget) {
1508
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1252
1509
  continue;
1253
1510
  }
1254
- const trimmed = commentText.trim();
1255
- if (tagName === "Field_displayName") {
1256
- displayName = trimmed;
1257
- displayNameTag = tag;
1258
- } else if (tagName === "Field_description") {
1259
- description = trimmed;
1260
- descriptionTag = tag;
1261
- }
1511
+ displayName ??= text;
1262
1512
  }
1263
- if (displayName !== void 0 && displayNameTag) {
1264
- annotations.push({
1265
- kind: "annotation",
1266
- annotationKind: "displayName",
1267
- value: displayName,
1268
- provenance: provenanceForJSDocTag(displayNameTag, file)
1269
- });
1270
- }
1271
- if (description !== void 0 && descriptionTag) {
1272
- annotations.push({
1273
- kind: "annotation",
1274
- annotationKind: "description",
1275
- value: description,
1276
- provenance: provenanceForJSDocTag(descriptionTag, file)
1277
- });
1278
- }
1279
- return { constraints, annotations };
1513
+ return {
1514
+ ...displayName !== void 0 && { displayName },
1515
+ memberDisplayNames
1516
+ };
1280
1517
  }
1281
1518
  function extractPathTarget(text) {
1282
1519
  const trimmed = text.trimStart();
@@ -1340,7 +1577,45 @@ function parseConstraintValue(tagName, text, provenance) {
1340
1577
  }
1341
1578
  return null;
1342
1579
  }
1580
+ if (expectedType === "boolean") {
1581
+ const trimmed = effectiveText.trim();
1582
+ if (trimmed !== "" && trimmed !== "true") {
1583
+ return null;
1584
+ }
1585
+ if (tagName === "uniqueItems") {
1586
+ return {
1587
+ kind: "constraint",
1588
+ constraintKind: "uniqueItems",
1589
+ value: true,
1590
+ ...path4 && { path: path4 },
1591
+ provenance
1592
+ };
1593
+ }
1594
+ return null;
1595
+ }
1343
1596
  if (expectedType === "json") {
1597
+ if (tagName === "const") {
1598
+ const trimmedText = effectiveText.trim();
1599
+ if (trimmedText === "") return null;
1600
+ try {
1601
+ const parsed2 = JSON.parse(trimmedText);
1602
+ return {
1603
+ kind: "constraint",
1604
+ constraintKind: "const",
1605
+ value: parsed2,
1606
+ ...path4 && { path: path4 },
1607
+ provenance
1608
+ };
1609
+ } catch {
1610
+ return {
1611
+ kind: "constraint",
1612
+ constraintKind: "const",
1613
+ value: trimmedText,
1614
+ ...path4 && { path: path4 },
1615
+ provenance
1616
+ };
1617
+ }
1618
+ }
1344
1619
  const parsed = tryParseJson(effectiveText);
1345
1620
  if (!Array.isArray(parsed)) {
1346
1621
  return null;
@@ -1372,6 +1647,34 @@ function parseConstraintValue(tagName, text, provenance) {
1372
1647
  provenance
1373
1648
  };
1374
1649
  }
1650
+ function parseDefaultValueValue(text, provenance) {
1651
+ const trimmed = text.trim();
1652
+ let value;
1653
+ if (trimmed === "null") {
1654
+ value = null;
1655
+ } else if (trimmed === "true") {
1656
+ value = true;
1657
+ } else if (trimmed === "false") {
1658
+ value = false;
1659
+ } else {
1660
+ const parsed = tryParseJson(trimmed);
1661
+ value = parsed !== null ? parsed : trimmed;
1662
+ }
1663
+ return {
1664
+ kind: "annotation",
1665
+ annotationKind: "defaultValue",
1666
+ value,
1667
+ provenance
1668
+ };
1669
+ }
1670
+ function isMemberTargetDisplayName(text) {
1671
+ return parseMemberTargetDisplayName(text) !== null;
1672
+ }
1673
+ function parseMemberTargetDisplayName(text) {
1674
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1675
+ if (!match?.[1] || !match[2]) return null;
1676
+ return { target: match[1], label: match[2].trim() };
1677
+ }
1375
1678
  function provenanceForComment(range, sourceFile, file, tagName) {
1376
1679
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1377
1680
  return {
@@ -1423,7 +1726,7 @@ var init_tsdoc_parser = __esm({
1423
1726
  minItems: "minItems",
1424
1727
  maxItems: "maxItems"
1425
1728
  };
1426
- TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1729
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1427
1730
  }
1428
1731
  });
1429
1732
 
@@ -1469,14 +1772,12 @@ function extractDefaultValueAnnotation(initializer, file = "") {
1469
1772
  }
1470
1773
  };
1471
1774
  }
1472
- var ts3, import_core4;
1775
+ var ts3;
1473
1776
  var init_jsdoc_constraints = __esm({
1474
1777
  "src/analyzer/jsdoc-constraints.ts"() {
1475
1778
  "use strict";
1476
1779
  ts3 = __toESM(require("typescript"), 1);
1477
- import_core4 = require("@formspec/core");
1478
1780
  init_tsdoc_parser();
1479
- init_json_utils();
1480
1781
  }
1481
1782
  });
1482
1783
 
@@ -1492,6 +1793,7 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1492
1793
  const fields = [];
1493
1794
  const fieldLayouts = [];
1494
1795
  const typeRegistry = {};
1796
+ const annotations = extractJSDocAnnotationNodes(classDecl, file);
1495
1797
  const visiting = /* @__PURE__ */ new Set();
1496
1798
  const instanceMethods = [];
1497
1799
  const staticMethods = [];
@@ -1514,12 +1816,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1514
1816
  }
1515
1817
  }
1516
1818
  }
1517
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1819
+ return {
1820
+ name,
1821
+ fields,
1822
+ fieldLayouts,
1823
+ typeRegistry,
1824
+ ...annotations.length > 0 && { annotations },
1825
+ instanceMethods,
1826
+ staticMethods
1827
+ };
1518
1828
  }
1519
1829
  function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1520
1830
  const name = interfaceDecl.name.text;
1521
1831
  const fields = [];
1522
1832
  const typeRegistry = {};
1833
+ const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1523
1834
  const visiting = /* @__PURE__ */ new Set();
1524
1835
  for (const member of interfaceDecl.members) {
1525
1836
  if (ts4.isPropertySignature(member)) {
@@ -1530,7 +1841,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1530
1841
  }
1531
1842
  }
1532
1843
  const fieldLayouts = fields.map(() => ({}));
1533
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1844
+ return {
1845
+ name,
1846
+ fields,
1847
+ fieldLayouts,
1848
+ typeRegistry,
1849
+ ...annotations.length > 0 && { annotations },
1850
+ instanceMethods: [],
1851
+ staticMethods: []
1852
+ };
1534
1853
  }
1535
1854
  function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1536
1855
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
@@ -1545,6 +1864,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1545
1864
  const name = typeAlias.name.text;
1546
1865
  const fields = [];
1547
1866
  const typeRegistry = {};
1867
+ const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1548
1868
  const visiting = /* @__PURE__ */ new Set();
1549
1869
  for (const member of typeAlias.type.members) {
1550
1870
  if (ts4.isPropertySignature(member)) {
@@ -1561,6 +1881,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1561
1881
  fields,
1562
1882
  fieldLayouts: fields.map(() => ({})),
1563
1883
  typeRegistry,
1884
+ ...annotations.length > 0 && { annotations },
1564
1885
  instanceMethods: [],
1565
1886
  staticMethods: []
1566
1887
  }
@@ -1574,18 +1895,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1574
1895
  const tsType = checker.getTypeAtLocation(prop);
1575
1896
  const optional = prop.questionToken !== void 0;
1576
1897
  const provenance = provenanceForNode(prop, file);
1577
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1898
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1578
1899
  const constraints = [];
1579
1900
  if (prop.type) {
1580
1901
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1581
1902
  }
1582
1903
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1583
- const annotations = [];
1904
+ let annotations = [];
1584
1905
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1585
1906
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1586
- if (defaultAnnotation) {
1907
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1587
1908
  annotations.push(defaultAnnotation);
1588
1909
  }
1910
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1589
1911
  return {
1590
1912
  kind: "field",
1591
1913
  name,
@@ -1604,14 +1926,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1604
1926
  const tsType = checker.getTypeAtLocation(prop);
1605
1927
  const optional = prop.questionToken !== void 0;
1606
1928
  const provenance = provenanceForNode(prop, file);
1607
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1929
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1608
1930
  const constraints = [];
1609
1931
  if (prop.type) {
1610
1932
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1611
1933
  }
1612
1934
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1613
- const annotations = [];
1935
+ let annotations = [];
1614
1936
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1937
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1615
1938
  return {
1616
1939
  kind: "field",
1617
1940
  name,
@@ -1622,7 +1945,69 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1622
1945
  provenance
1623
1946
  };
1624
1947
  }
1625
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1948
+ function applyEnumMemberDisplayNames(type, annotations) {
1949
+ if (!annotations.some(
1950
+ (annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
1951
+ )) {
1952
+ return { type, annotations: [...annotations] };
1953
+ }
1954
+ const consumed = /* @__PURE__ */ new Set();
1955
+ const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
1956
+ if (consumed.size === 0) {
1957
+ return { type, annotations: [...annotations] };
1958
+ }
1959
+ return {
1960
+ type: nextType,
1961
+ annotations: annotations.filter((annotation) => !consumed.has(annotation))
1962
+ };
1963
+ }
1964
+ function rewriteEnumDisplayNames(type, annotations, consumed) {
1965
+ switch (type.kind) {
1966
+ case "enum":
1967
+ return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
1968
+ case "union": {
1969
+ return {
1970
+ ...type,
1971
+ members: type.members.map(
1972
+ (member) => rewriteEnumDisplayNames(member, annotations, consumed)
1973
+ )
1974
+ };
1975
+ }
1976
+ default:
1977
+ return type;
1978
+ }
1979
+ }
1980
+ function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
1981
+ const displayNames = /* @__PURE__ */ new Map();
1982
+ for (const annotation of annotations) {
1983
+ if (annotation.annotationKind !== "displayName") continue;
1984
+ const parsed = parseEnumMemberDisplayName(annotation.value);
1985
+ if (!parsed) continue;
1986
+ consumed.add(annotation);
1987
+ const member = type.members.find((m) => String(m.value) === parsed.value);
1988
+ if (!member) continue;
1989
+ displayNames.set(String(member.value), parsed.label);
1990
+ }
1991
+ if (displayNames.size === 0) {
1992
+ return type;
1993
+ }
1994
+ return {
1995
+ ...type,
1996
+ members: type.members.map((member) => {
1997
+ const displayName = displayNames.get(String(member.value));
1998
+ return displayName !== void 0 ? { ...member, displayName } : member;
1999
+ })
2000
+ };
2001
+ }
2002
+ function parseEnumMemberDisplayName(value) {
2003
+ const trimmed = value.trim();
2004
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
2005
+ if (!match?.[1] || !match[2]) return null;
2006
+ const label = match[2].trim();
2007
+ if (label === "") return null;
2008
+ return { value: match[1], label };
2009
+ }
2010
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1626
2011
  if (type.flags & ts4.TypeFlags.String) {
1627
2012
  return { kind: "primitive", primitiveKind: "string" };
1628
2013
  }
@@ -1651,7 +2036,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1651
2036
  };
1652
2037
  }
1653
2038
  if (type.isUnion()) {
1654
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
2039
+ return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1655
2040
  }
1656
2041
  if (checker.isArrayType(type)) {
1657
2042
  return resolveArrayType(type, checker, file, typeRegistry, visiting);
@@ -1661,70 +2046,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1661
2046
  }
1662
2047
  return { kind: "primitive", primitiveKind: "string" };
1663
2048
  }
1664
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
2049
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
2050
+ const typeName = getNamedTypeName(type);
2051
+ const namedDecl = getNamedTypeDeclaration(type);
2052
+ if (typeName && typeName in typeRegistry) {
2053
+ return { kind: "reference", name: typeName, typeArguments: [] };
2054
+ }
1665
2055
  const allTypes = type.types;
1666
2056
  const nonNullTypes = allTypes.filter(
1667
2057
  (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1668
2058
  );
1669
2059
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2060
+ const memberDisplayNames = /* @__PURE__ */ new Map();
2061
+ if (namedDecl) {
2062
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
2063
+ memberDisplayNames.set(value, label);
2064
+ }
2065
+ }
2066
+ if (sourceNode) {
2067
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
2068
+ memberDisplayNames.set(value, label);
2069
+ }
2070
+ }
2071
+ const registerNamed = (result) => {
2072
+ if (!typeName) {
2073
+ return result;
2074
+ }
2075
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2076
+ typeRegistry[typeName] = {
2077
+ name: typeName,
2078
+ type: result,
2079
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2080
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
2081
+ };
2082
+ return { kind: "reference", name: typeName, typeArguments: [] };
2083
+ };
2084
+ const applyMemberLabels = (members2) => members2.map((value) => {
2085
+ const displayName = memberDisplayNames.get(String(value));
2086
+ return displayName !== void 0 ? { value, displayName } : { value };
2087
+ });
1670
2088
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1671
2089
  if (isBooleanUnion2) {
1672
2090
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1673
- if (hasNull) {
1674
- return {
1675
- kind: "union",
1676
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1677
- };
1678
- }
1679
- return boolNode;
2091
+ const result = hasNull ? {
2092
+ kind: "union",
2093
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
2094
+ } : boolNode;
2095
+ return registerNamed(result);
1680
2096
  }
1681
2097
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1682
2098
  if (allStringLiterals && nonNullTypes.length > 0) {
1683
2099
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1684
2100
  const enumNode = {
1685
2101
  kind: "enum",
1686
- members: stringTypes.map((t) => ({ value: t.value }))
2102
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1687
2103
  };
1688
- if (hasNull) {
1689
- return {
1690
- kind: "union",
1691
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1692
- };
1693
- }
1694
- return enumNode;
2104
+ const result = hasNull ? {
2105
+ kind: "union",
2106
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2107
+ } : enumNode;
2108
+ return registerNamed(result);
1695
2109
  }
1696
2110
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1697
2111
  if (allNumberLiterals && nonNullTypes.length > 0) {
1698
2112
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1699
2113
  const enumNode = {
1700
2114
  kind: "enum",
1701
- members: numberTypes.map((t) => ({ value: t.value }))
2115
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1702
2116
  };
1703
- if (hasNull) {
1704
- return {
1705
- kind: "union",
1706
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1707
- };
1708
- }
1709
- return enumNode;
2117
+ const result = hasNull ? {
2118
+ kind: "union",
2119
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2120
+ } : enumNode;
2121
+ return registerNamed(result);
1710
2122
  }
1711
2123
  if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1712
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1713
- if (hasNull) {
1714
- return {
1715
- kind: "union",
1716
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1717
- };
1718
- }
1719
- return inner;
2124
+ const inner = resolveTypeNode(
2125
+ nonNullTypes[0],
2126
+ checker,
2127
+ file,
2128
+ typeRegistry,
2129
+ visiting,
2130
+ sourceNode
2131
+ );
2132
+ const result = hasNull ? {
2133
+ kind: "union",
2134
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2135
+ } : inner;
2136
+ return registerNamed(result);
1720
2137
  }
1721
2138
  const members = nonNullTypes.map(
1722
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2139
+ (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1723
2140
  );
1724
2141
  if (hasNull) {
1725
2142
  members.push({ kind: "primitive", primitiveKind: "null" });
1726
2143
  }
1727
- return { kind: "union", members };
2144
+ return registerNamed({ kind: "union", members });
1728
2145
  }
1729
2146
  function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1730
2147
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
@@ -1732,15 +2149,92 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1732
2149
  const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1733
2150
  return { kind: "array", items };
1734
2151
  }
2152
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2153
+ if (type.getProperties().length > 0) {
2154
+ return null;
2155
+ }
2156
+ const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
2157
+ if (!indexInfo) {
2158
+ return null;
2159
+ }
2160
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2161
+ return { kind: "record", valueType };
2162
+ }
2163
+ function typeNodeContainsReference(type, targetName) {
2164
+ switch (type.kind) {
2165
+ case "reference":
2166
+ return type.name === targetName;
2167
+ case "array":
2168
+ return typeNodeContainsReference(type.items, targetName);
2169
+ case "record":
2170
+ return typeNodeContainsReference(type.valueType, targetName);
2171
+ case "union":
2172
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2173
+ case "object":
2174
+ return type.properties.some(
2175
+ (property) => typeNodeContainsReference(property.type, targetName)
2176
+ );
2177
+ case "primitive":
2178
+ case "enum":
2179
+ case "dynamic":
2180
+ case "custom":
2181
+ return false;
2182
+ default: {
2183
+ const _exhaustive = type;
2184
+ return _exhaustive;
2185
+ }
2186
+ }
2187
+ }
1735
2188
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2189
+ const typeName = getNamedTypeName(type);
2190
+ const namedTypeName = typeName ?? void 0;
2191
+ const namedDecl = getNamedTypeDeclaration(type);
2192
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2193
+ const clearNamedTypeRegistration = () => {
2194
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2195
+ return;
2196
+ }
2197
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2198
+ };
1736
2199
  if (visiting.has(type)) {
2200
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2201
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2202
+ }
1737
2203
  return { kind: "object", properties: [], additionalProperties: false };
1738
2204
  }
2205
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2206
+ typeRegistry[namedTypeName] = {
2207
+ name: namedTypeName,
2208
+ type: RESOLVING_TYPE_PLACEHOLDER,
2209
+ provenance: provenanceForDeclaration(namedDecl, file)
2210
+ };
2211
+ }
1739
2212
  visiting.add(type);
1740
- const typeName = getNamedTypeName(type);
1741
- if (typeName && typeName in typeRegistry) {
2213
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2214
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2215
+ visiting.delete(type);
2216
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2217
+ }
2218
+ }
2219
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2220
+ if (recordNode) {
1742
2221
  visiting.delete(type);
1743
- return { kind: "reference", name: typeName, typeArguments: [] };
2222
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2223
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2224
+ if (!isRecursiveRecord) {
2225
+ clearNamedTypeRegistration();
2226
+ return recordNode;
2227
+ }
2228
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2229
+ typeRegistry[namedTypeName] = {
2230
+ name: namedTypeName,
2231
+ type: recordNode,
2232
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2233
+ provenance: provenanceForDeclaration(namedDecl, file)
2234
+ };
2235
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2236
+ }
2237
+ return recordNode;
1744
2238
  }
1745
2239
  const properties = [];
1746
2240
  const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
@@ -1749,7 +2243,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1749
2243
  if (!declaration) continue;
1750
2244
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1751
2245
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1752
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2246
+ const propTypeNode = resolveTypeNode(
2247
+ propType,
2248
+ checker,
2249
+ file,
2250
+ typeRegistry,
2251
+ visiting,
2252
+ declaration
2253
+ );
1753
2254
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1754
2255
  properties.push({
1755
2256
  name: prop.name,
@@ -1764,15 +2265,17 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1764
2265
  const objectNode = {
1765
2266
  kind: "object",
1766
2267
  properties,
1767
- additionalProperties: false
2268
+ additionalProperties: true
1768
2269
  };
1769
- if (typeName) {
1770
- typeRegistry[typeName] = {
1771
- name: typeName,
2270
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2271
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2272
+ typeRegistry[namedTypeName] = {
2273
+ name: namedTypeName,
1772
2274
  type: objectNode,
1773
- provenance: provenanceForFile(file)
2275
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2276
+ provenance: provenanceForDeclaration(namedDecl, file)
1774
2277
  };
1775
- return { kind: "reference", name: typeName, typeArguments: [] };
2278
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1776
2279
  }
1777
2280
  return objectNode;
1778
2281
  }
@@ -1863,6 +2366,12 @@ function provenanceForNode(node, file) {
1863
2366
  function provenanceForFile(file) {
1864
2367
  return { surface: "tsdoc", file, line: 0, column: 0 };
1865
2368
  }
2369
+ function provenanceForDeclaration(node, file) {
2370
+ if (!node) {
2371
+ return provenanceForFile(file);
2372
+ }
2373
+ return provenanceForNode(node, file);
2374
+ }
1866
2375
  function getNamedTypeName(type) {
1867
2376
  const symbol = type.getSymbol();
1868
2377
  if (symbol?.declarations) {
@@ -1881,6 +2390,20 @@ function getNamedTypeName(type) {
1881
2390
  }
1882
2391
  return null;
1883
2392
  }
2393
+ function getNamedTypeDeclaration(type) {
2394
+ const symbol = type.getSymbol();
2395
+ if (symbol?.declarations) {
2396
+ const decl = symbol.declarations[0];
2397
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2398
+ return decl;
2399
+ }
2400
+ }
2401
+ const aliasSymbol = type.aliasSymbol;
2402
+ if (aliasSymbol?.declarations) {
2403
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2404
+ }
2405
+ return void 0;
2406
+ }
1884
2407
  function analyzeMethod(method, checker) {
1885
2408
  if (!ts4.isIdentifier(method.name)) {
1886
2409
  return null;
@@ -1921,12 +2444,18 @@ function detectFormSpecReference(typeNode) {
1921
2444
  }
1922
2445
  return null;
1923
2446
  }
1924
- var ts4, MAX_ALIAS_CHAIN_DEPTH;
2447
+ var ts4, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
1925
2448
  var init_class_analyzer = __esm({
1926
2449
  "src/analyzer/class-analyzer.ts"() {
1927
2450
  "use strict";
1928
2451
  ts4 = __toESM(require("typescript"), 1);
1929
2452
  init_jsdoc_constraints();
2453
+ init_tsdoc_parser();
2454
+ RESOLVING_TYPE_PLACEHOLDER = {
2455
+ kind: "object",
2456
+ properties: [],
2457
+ additionalProperties: true
2458
+ };
1930
2459
  MAX_ALIAS_CHAIN_DEPTH = 8;
1931
2460
  }
1932
2461
  });
@@ -1984,14 +2513,226 @@ var init_class_schema = __esm({
1984
2513
  }
1985
2514
  });
1986
2515
 
2516
+ // src/generators/mixed-authoring.ts
2517
+ function buildMixedAuthoringSchemas(options) {
2518
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2519
+ const analysis = analyzeNamedType(filePath, typeName);
2520
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2521
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2522
+ return {
2523
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2524
+ uiSchema: generateUiSchemaFromIR(ir)
2525
+ };
2526
+ }
2527
+ function analyzeNamedType(filePath, typeName) {
2528
+ const ctx = createProgramContext(filePath);
2529
+ const source = { file: filePath };
2530
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2531
+ if (classDecl !== null) {
2532
+ return analyzeClassToIR(classDecl, ctx.checker, source.file);
2533
+ }
2534
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2535
+ if (interfaceDecl !== null) {
2536
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2537
+ }
2538
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2539
+ if (typeAlias !== null) {
2540
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2541
+ if (result.ok) {
2542
+ return result.analysis;
2543
+ }
2544
+ throw new Error(result.error);
2545
+ }
2546
+ throw new Error(
2547
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2548
+ );
2549
+ }
2550
+ function composeAnalysisWithOverlays(analysis, overlays) {
2551
+ const overlayIR = canonicalizeChainDSL(overlays);
2552
+ const overlayFields = collectOverlayFields(overlayIR.elements);
2553
+ if (overlayFields.length === 0) {
2554
+ return analysis;
2555
+ }
2556
+ const overlayByName = /* @__PURE__ */ new Map();
2557
+ for (const field of overlayFields) {
2558
+ if (overlayByName.has(field.name)) {
2559
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
2560
+ }
2561
+ overlayByName.set(field.name, field);
2562
+ }
2563
+ const mergedFields = [];
2564
+ for (const baseField of analysis.fields) {
2565
+ const overlayField = overlayByName.get(baseField.name);
2566
+ if (overlayField === void 0) {
2567
+ mergedFields.push(baseField);
2568
+ continue;
2569
+ }
2570
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
2571
+ overlayByName.delete(baseField.name);
2572
+ }
2573
+ if (overlayByName.size > 0) {
2574
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
2575
+ throw new Error(
2576
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
2577
+ );
2578
+ }
2579
+ return {
2580
+ ...analysis,
2581
+ fields: mergedFields
2582
+ };
2583
+ }
2584
+ function collectOverlayFields(elements) {
2585
+ const fields = [];
2586
+ for (const element of elements) {
2587
+ switch (element.kind) {
2588
+ case "field":
2589
+ fields.push(element);
2590
+ break;
2591
+ case "group":
2592
+ fields.push(...collectOverlayFields(element.elements));
2593
+ break;
2594
+ case "conditional":
2595
+ fields.push(...collectOverlayFields(element.elements));
2596
+ break;
2597
+ default: {
2598
+ const _exhaustive = element;
2599
+ void _exhaustive;
2600
+ }
2601
+ }
2602
+ }
2603
+ return fields;
2604
+ }
2605
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
2606
+ assertSupportedOverlayField(baseField, overlayField);
2607
+ return {
2608
+ ...baseField,
2609
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
2610
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
2611
+ };
2612
+ }
2613
+ function assertSupportedOverlayField(baseField, overlayField) {
2614
+ if (overlayField.constraints.length > 0) {
2615
+ throw new Error(
2616
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2617
+ );
2618
+ }
2619
+ if (overlayField.required) {
2620
+ throw new Error(
2621
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2622
+ );
2623
+ }
2624
+ }
2625
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
2626
+ const { type: baseType } = baseField;
2627
+ const { type: overlayType } = overlayField;
2628
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
2629
+ throw new Error(
2630
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
2631
+ );
2632
+ }
2633
+ if (overlayType.kind === "dynamic") {
2634
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
2635
+ throw new Error(
2636
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
2637
+ );
2638
+ }
2639
+ return overlayType;
2640
+ }
2641
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
2642
+ throw new Error(
2643
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
2644
+ );
2645
+ }
2646
+ return baseType;
2647
+ }
2648
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
2649
+ const overlayType = overlayField.type;
2650
+ if (overlayType.kind !== "dynamic") {
2651
+ return false;
2652
+ }
2653
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
2654
+ if (resolvedBaseType === null) {
2655
+ return false;
2656
+ }
2657
+ if (overlayType.dynamicKind === "enum") {
2658
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
2659
+ }
2660
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
2661
+ }
2662
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
2663
+ if (type.kind !== "reference") {
2664
+ return type;
2665
+ }
2666
+ if (seen.has(type.name)) {
2667
+ return null;
2668
+ }
2669
+ const definition = typeRegistry[type.name];
2670
+ if (definition === void 0) {
2671
+ return null;
2672
+ }
2673
+ seen.add(type.name);
2674
+ return resolveReferenceType(definition.type, typeRegistry, seen);
2675
+ }
2676
+ function isSameStaticTypeShape(baseType, overlayType) {
2677
+ if (baseType.kind !== overlayType.kind) {
2678
+ return false;
2679
+ }
2680
+ switch (baseType.kind) {
2681
+ case "primitive":
2682
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
2683
+ case "enum":
2684
+ return overlayType.kind === "enum";
2685
+ case "dynamic":
2686
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
2687
+ case "record":
2688
+ return overlayType.kind === "record";
2689
+ case "reference":
2690
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
2691
+ case "union":
2692
+ return overlayType.kind === "union";
2693
+ case "custom":
2694
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
2695
+ case "object":
2696
+ case "array":
2697
+ return true;
2698
+ default: {
2699
+ const _exhaustive = baseType;
2700
+ return _exhaustive;
2701
+ }
2702
+ }
2703
+ }
2704
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2705
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
2706
+ const overlayOnly = overlayAnnotations.filter(
2707
+ (annotation) => !baseKeys.has(annotationKey(annotation))
2708
+ );
2709
+ return [...overlayOnly, ...baseAnnotations];
2710
+ }
2711
+ function annotationKey(annotation) {
2712
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
2713
+ }
2714
+ var init_mixed_authoring = __esm({
2715
+ "src/generators/mixed-authoring.ts"() {
2716
+ "use strict";
2717
+ init_ir_generator();
2718
+ init_ir_generator2();
2719
+ init_canonicalize();
2720
+ init_program();
2721
+ init_class_analyzer();
2722
+ }
2723
+ });
2724
+
1987
2725
  // src/index.ts
1988
2726
  var index_exports = {};
1989
2727
  __export(index_exports, {
1990
2728
  buildFormSchemas: () => buildFormSchemas,
2729
+ buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
1991
2730
  categorizationSchema: () => categorizationSchema,
1992
2731
  categorySchema: () => categorySchema,
1993
2732
  controlSchema: () => controlSchema,
2733
+ createExtensionRegistry: () => createExtensionRegistry,
1994
2734
  generateJsonSchema: () => generateJsonSchema,
2735
+ generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
1995
2736
  generateSchemas: () => generateSchemas,
1996
2737
  generateSchemasFromClass: () => generateSchemasFromClass,
1997
2738
  generateUiSchema: () => generateUiSchema,
@@ -2012,15 +2753,19 @@ __export(index_exports, {
2012
2753
  verticalLayoutSchema: () => verticalLayoutSchema,
2013
2754
  writeSchemas: () => writeSchemas
2014
2755
  });
2015
- function buildFormSchemas(form) {
2756
+ function buildFormSchemas(form, options) {
2016
2757
  return {
2017
- jsonSchema: generateJsonSchema(form),
2758
+ jsonSchema: generateJsonSchema(form, options),
2018
2759
  uiSchema: generateUiSchema(form)
2019
2760
  };
2020
2761
  }
2021
2762
  function writeSchemas(form, options) {
2022
- const { outDir, name = "schema", indent = 2 } = options;
2023
- const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
2763
+ const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
2764
+ const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
2765
+ extensionRegistry,
2766
+ vendorPrefix
2767
+ };
2768
+ const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
2024
2769
  if (!fs.existsSync(outDir)) {
2025
2770
  fs.mkdirSync(outDir, { recursive: true });
2026
2771
  }
@@ -2036,14 +2781,18 @@ var init_index = __esm({
2036
2781
  "use strict";
2037
2782
  init_generator();
2038
2783
  init_generator2();
2784
+ init_ir_generator();
2039
2785
  fs = __toESM(require("fs"), 1);
2040
2786
  path2 = __toESM(require("path"), 1);
2041
2787
  init_types();
2788
+ init_extensions();
2042
2789
  init_schema();
2043
2790
  init_schema2();
2044
2791
  init_generator();
2792
+ init_ir_generator();
2045
2793
  init_generator2();
2046
2794
  init_class_schema();
2795
+ init_mixed_authoring();
2047
2796
  }
2048
2797
  });
2049
2798