@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.js CHANGED
@@ -177,7 +177,7 @@ function canonicalizeArrayField(field) {
177
177
  const itemsType = {
178
178
  kind: "object",
179
179
  properties: itemProperties,
180
- additionalProperties: false
180
+ additionalProperties: true
181
181
  };
182
182
  const type = { kind: "array", items: itemsType };
183
183
  const constraints = [];
@@ -212,7 +212,7 @@ function canonicalizeObjectField(field) {
212
212
  const type = {
213
213
  kind: "object",
214
214
  properties,
215
- additionalProperties: false
215
+ additionalProperties: true
216
216
  };
217
217
  return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
218
218
  }
@@ -325,6 +325,7 @@ function canonicalizeTSDoc(analysis, source) {
325
325
  irVersion: IR_VERSION2,
326
326
  elements,
327
327
  typeRegistry: analysis.typeRegistry,
328
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
328
329
  provenance
329
330
  };
330
331
  }
@@ -384,13 +385,26 @@ function wrapInConditional(field, layout, provenance) {
384
385
  }
385
386
 
386
387
  // src/json-schema/ir-generator.ts
387
- function makeContext() {
388
- return { defs: {} };
388
+ function makeContext(options) {
389
+ const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
390
+ if (!vendorPrefix.startsWith("x-")) {
391
+ throw new Error(
392
+ `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
393
+ );
394
+ }
395
+ return {
396
+ defs: {},
397
+ extensionRegistry: options?.extensionRegistry,
398
+ vendorPrefix
399
+ };
389
400
  }
390
- function generateJsonSchemaFromIR(ir) {
391
- const ctx = makeContext();
401
+ function generateJsonSchemaFromIR(ir, options) {
402
+ const ctx = makeContext(options);
392
403
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
393
404
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
405
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
406
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
407
+ }
394
408
  }
395
409
  const properties = {};
396
410
  const required = [];
@@ -402,6 +416,9 @@ function generateJsonSchemaFromIR(ir) {
402
416
  properties,
403
417
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
404
418
  };
419
+ if (ir.annotations && ir.annotations.length > 0) {
420
+ applyAnnotations(result, ir.annotations, ctx);
421
+ }
405
422
  if (Object.keys(ctx.defs).length > 0) {
406
423
  result.$defs = ctx.defs;
407
424
  }
@@ -431,25 +448,54 @@ function collectFields(elements, properties, required, ctx) {
431
448
  }
432
449
  function generateFieldSchema(field, ctx) {
433
450
  const schema = generateTypeNode(field.type, ctx);
451
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
434
452
  const directConstraints = [];
453
+ const itemConstraints = [];
435
454
  const pathConstraints = [];
436
455
  for (const c of field.constraints) {
437
456
  if (c.path) {
438
457
  pathConstraints.push(c);
458
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
459
+ itemConstraints.push(c);
439
460
  } else {
440
461
  directConstraints.push(c);
441
462
  }
442
463
  }
443
- applyConstraints(schema, directConstraints);
444
- applyAnnotations(schema, field.annotations);
464
+ applyConstraints(schema, directConstraints, ctx);
465
+ if (itemStringSchema !== void 0) {
466
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
467
+ }
468
+ const rootAnnotations = [];
469
+ const itemAnnotations = [];
470
+ for (const annotation of field.annotations) {
471
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
472
+ itemAnnotations.push(annotation);
473
+ } else {
474
+ rootAnnotations.push(annotation);
475
+ }
476
+ }
477
+ applyAnnotations(schema, rootAnnotations, ctx);
478
+ if (itemStringSchema !== void 0) {
479
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
480
+ }
445
481
  if (pathConstraints.length === 0) {
446
482
  return schema;
447
483
  }
448
- return applyPathTargetedConstraints(schema, pathConstraints);
484
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx);
449
485
  }
450
- function applyPathTargetedConstraints(schema, pathConstraints) {
486
+ function isStringItemConstraint(constraint) {
487
+ switch (constraint.constraintKind) {
488
+ case "minLength":
489
+ case "maxLength":
490
+ case "pattern":
491
+ return true;
492
+ default:
493
+ return false;
494
+ }
495
+ }
496
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
451
497
  if (schema.type === "array" && schema.items) {
452
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
498
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
453
499
  return schema;
454
500
  }
455
501
  const byTarget = /* @__PURE__ */ new Map();
@@ -463,7 +509,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
463
509
  const propertyOverrides = {};
464
510
  for (const [target, constraints] of byTarget) {
465
511
  const subSchema = {};
466
- applyConstraints(subSchema, constraints);
512
+ applyConstraints(subSchema, constraints, ctx);
467
513
  propertyOverrides[target] = subSchema;
468
514
  }
469
515
  if (schema.$ref) {
@@ -507,6 +553,8 @@ function generateTypeNode(type, ctx) {
507
553
  return generateArrayType(type, ctx);
508
554
  case "object":
509
555
  return generateObjectType(type, ctx);
556
+ case "record":
557
+ return generateRecordType(type, ctx);
510
558
  case "union":
511
559
  return generateUnionType(type, ctx);
512
560
  case "reference":
@@ -514,7 +562,7 @@ function generateTypeNode(type, ctx) {
514
562
  case "dynamic":
515
563
  return generateDynamicType(type);
516
564
  case "custom":
517
- return generateCustomType(type);
565
+ return generateCustomType(type, ctx);
518
566
  default: {
519
567
  const _exhaustive = type;
520
568
  return _exhaustive;
@@ -563,16 +611,27 @@ function generateObjectType(type, ctx) {
563
611
  }
564
612
  return schema;
565
613
  }
614
+ function generateRecordType(type, ctx) {
615
+ return {
616
+ type: "object",
617
+ additionalProperties: generateTypeNode(type.valueType, ctx)
618
+ };
619
+ }
566
620
  function generatePropertySchema(prop, ctx) {
567
621
  const schema = generateTypeNode(prop.type, ctx);
568
- applyConstraints(schema, prop.constraints);
569
- applyAnnotations(schema, prop.annotations);
622
+ applyConstraints(schema, prop.constraints, ctx);
623
+ applyAnnotations(schema, prop.annotations, ctx);
570
624
  return schema;
571
625
  }
572
626
  function generateUnionType(type, ctx) {
573
627
  if (isBooleanUnion(type)) {
574
628
  return { type: "boolean" };
575
629
  }
630
+ if (isNullableUnion(type)) {
631
+ return {
632
+ oneOf: type.members.map((m) => generateTypeNode(m, ctx))
633
+ };
634
+ }
576
635
  return {
577
636
  anyOf: type.members.map((m) => generateTypeNode(m, ctx))
578
637
  };
@@ -582,6 +641,13 @@ function isBooleanUnion(type) {
582
641
  const kinds = type.members.map((m) => m.kind);
583
642
  return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
584
643
  }
644
+ function isNullableUnion(type) {
645
+ if (type.members.length !== 2) return false;
646
+ const nullCount = type.members.filter(
647
+ (m) => m.kind === "primitive" && m.primitiveKind === "null"
648
+ ).length;
649
+ return nullCount === 1;
650
+ }
585
651
  function generateReferenceType(type) {
586
652
  return { $ref: `#/$defs/${type.name}` };
587
653
  }
@@ -602,10 +668,7 @@ function generateDynamicType(type) {
602
668
  "x-formspec-schemaSource": type.sourceKey
603
669
  };
604
670
  }
605
- function generateCustomType(_type) {
606
- return { type: "object" };
607
- }
608
- function applyConstraints(schema, constraints) {
671
+ function applyConstraints(schema, constraints, ctx) {
609
672
  for (const constraint of constraints) {
610
673
  switch (constraint.constraintKind) {
611
674
  case "minimum":
@@ -647,9 +710,13 @@ function applyConstraints(schema, constraints) {
647
710
  case "uniqueItems":
648
711
  schema.uniqueItems = constraint.value;
649
712
  break;
713
+ case "const":
714
+ schema.const = constraint.value;
715
+ break;
650
716
  case "allowedMembers":
651
717
  break;
652
718
  case "custom":
719
+ applyCustomConstraint(schema, constraint, ctx);
653
720
  break;
654
721
  default: {
655
722
  const _exhaustive = constraint;
@@ -658,7 +725,7 @@ function applyConstraints(schema, constraints) {
658
725
  }
659
726
  }
660
727
  }
661
- function applyAnnotations(schema, annotations) {
728
+ function applyAnnotations(schema, annotations, ctx) {
662
729
  for (const annotation of annotations) {
663
730
  switch (annotation.annotationKind) {
664
731
  case "displayName":
@@ -670,14 +737,21 @@ function applyAnnotations(schema, annotations) {
670
737
  case "defaultValue":
671
738
  schema.default = annotation.value;
672
739
  break;
740
+ case "format":
741
+ schema.format = annotation.value;
742
+ break;
673
743
  case "deprecated":
674
744
  schema.deprecated = true;
745
+ if (annotation.message !== void 0 && annotation.message !== "") {
746
+ schema["x-formspec-deprecation-description"] = annotation.message;
747
+ }
675
748
  break;
676
749
  case "placeholder":
677
750
  break;
678
751
  case "formatHint":
679
752
  break;
680
753
  case "custom":
754
+ applyCustomAnnotation(schema, annotation, ctx);
681
755
  break;
682
756
  default: {
683
757
  const _exhaustive = annotation;
@@ -686,11 +760,41 @@ function applyAnnotations(schema, annotations) {
686
760
  }
687
761
  }
688
762
  }
763
+ function generateCustomType(type, ctx) {
764
+ const registration = ctx.extensionRegistry?.findType(type.typeId);
765
+ if (registration === void 0) {
766
+ throw new Error(
767
+ `Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
768
+ );
769
+ }
770
+ return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
771
+ }
772
+ function applyCustomConstraint(schema, constraint, ctx) {
773
+ const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
774
+ if (registration === void 0) {
775
+ throw new Error(
776
+ `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
777
+ );
778
+ }
779
+ Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
780
+ }
781
+ function applyCustomAnnotation(schema, annotation, ctx) {
782
+ const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
783
+ if (registration === void 0) {
784
+ throw new Error(
785
+ `Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
786
+ );
787
+ }
788
+ if (registration.toJsonSchema === void 0) {
789
+ return;
790
+ }
791
+ Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
792
+ }
689
793
 
690
794
  // src/json-schema/generator.ts
691
- function generateJsonSchema(form) {
795
+ function generateJsonSchema(form, options) {
692
796
  const ir = canonicalizeChainDSL(form);
693
- return generateJsonSchemaFromIR(ir);
797
+ return generateJsonSchemaFromIR(ir, options);
694
798
  }
695
799
 
696
800
  // src/ui-schema/schema.ts
@@ -828,25 +932,31 @@ function createShowRule(fieldName, value) {
828
932
  }
829
933
  };
830
934
  }
935
+ function flattenConditionSchema(scope, schema) {
936
+ if (schema.allOf === void 0) {
937
+ if (scope === "#") {
938
+ return [schema];
939
+ }
940
+ const fieldName = scope.replace("#/properties/", "");
941
+ return [
942
+ {
943
+ properties: {
944
+ [fieldName]: schema
945
+ }
946
+ }
947
+ ];
948
+ }
949
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
950
+ }
831
951
  function combineRules(parentRule, childRule) {
832
- const parentCondition = parentRule.condition;
833
- const childCondition = childRule.condition;
834
952
  return {
835
953
  effect: "SHOW",
836
954
  condition: {
837
955
  scope: "#",
838
956
  schema: {
839
957
  allOf: [
840
- {
841
- properties: {
842
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
843
- }
844
- },
845
- {
846
- properties: {
847
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
848
- }
849
- }
958
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
959
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
850
960
  ]
851
961
  }
852
962
  }
@@ -854,10 +964,14 @@ function combineRules(parentRule, childRule) {
854
964
  }
855
965
  function fieldNodeToControl(field, parentRule) {
856
966
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
967
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
857
968
  const control = {
858
969
  type: "Control",
859
970
  scope: fieldToScope(field.name),
860
971
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
972
+ ...placeholderAnnotation !== void 0 && {
973
+ options: { placeholder: placeholderAnnotation.value }
974
+ },
861
975
  ...parentRule !== void 0 && { rule: parentRule }
862
976
  };
863
977
  return control;
@@ -924,6 +1038,48 @@ function getSchemaExtension(schema, key) {
924
1038
  return schema[key];
925
1039
  }
926
1040
 
1041
+ // src/extensions/registry.ts
1042
+ function createExtensionRegistry(extensions) {
1043
+ const typeMap = /* @__PURE__ */ new Map();
1044
+ const constraintMap = /* @__PURE__ */ new Map();
1045
+ const annotationMap = /* @__PURE__ */ new Map();
1046
+ for (const ext of extensions) {
1047
+ if (ext.types !== void 0) {
1048
+ for (const type of ext.types) {
1049
+ const qualifiedId = `${ext.extensionId}/${type.typeName}`;
1050
+ if (typeMap.has(qualifiedId)) {
1051
+ throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1052
+ }
1053
+ typeMap.set(qualifiedId, type);
1054
+ }
1055
+ }
1056
+ if (ext.constraints !== void 0) {
1057
+ for (const constraint of ext.constraints) {
1058
+ const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
1059
+ if (constraintMap.has(qualifiedId)) {
1060
+ throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
1061
+ }
1062
+ constraintMap.set(qualifiedId, constraint);
1063
+ }
1064
+ }
1065
+ if (ext.annotations !== void 0) {
1066
+ for (const annotation of ext.annotations) {
1067
+ const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
1068
+ if (annotationMap.has(qualifiedId)) {
1069
+ throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
1070
+ }
1071
+ annotationMap.set(qualifiedId, annotation);
1072
+ }
1073
+ }
1074
+ }
1075
+ return {
1076
+ extensions,
1077
+ findType: (typeId) => typeMap.get(typeId),
1078
+ findConstraint: (constraintId) => constraintMap.get(constraintId),
1079
+ findAnnotation: (annotationId) => annotationMap.get(annotationId)
1080
+ };
1081
+ }
1082
+
927
1083
  // src/json-schema/schema.ts
928
1084
  import { z as z3 } from "zod";
929
1085
  var jsonSchemaTypeSchema = z3.enum([
@@ -1063,11 +1219,6 @@ import * as ts4 from "typescript";
1063
1219
 
1064
1220
  // src/analyzer/jsdoc-constraints.ts
1065
1221
  import * as ts3 from "typescript";
1066
- import {
1067
- BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
1068
- isBuiltinConstraintName as isBuiltinConstraintName2,
1069
- normalizeConstraintTagName as normalizeConstraintTagName2
1070
- } from "@formspec/core";
1071
1222
 
1072
1223
  // src/analyzer/tsdoc-parser.ts
1073
1224
  import * as ts2 from "typescript";
@@ -1109,7 +1260,7 @@ var LENGTH_CONSTRAINT_MAP = {
1109
1260
  minItems: "minItems",
1110
1261
  maxItems: "maxItems"
1111
1262
  };
1112
- var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1263
+ var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1113
1264
  function createFormSpecTSDocConfig() {
1114
1265
  const config = new TSDocConfiguration();
1115
1266
  for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
@@ -1121,6 +1272,15 @@ function createFormSpecTSDocConfig() {
1121
1272
  })
1122
1273
  );
1123
1274
  }
1275
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1276
+ config.addTagDefinition(
1277
+ new TSDocTagDefinition({
1278
+ tagName: "@" + tagName,
1279
+ syntaxKind: TSDocTagSyntaxKind.BlockTag,
1280
+ allowMultiple: true
1281
+ })
1282
+ );
1283
+ }
1124
1284
  return config;
1125
1285
  }
1126
1286
  var sharedParser;
@@ -1131,6 +1291,12 @@ function getParser() {
1131
1291
  function parseTSDocTags(node, file = "") {
1132
1292
  const constraints = [];
1133
1293
  const annotations = [];
1294
+ let displayName;
1295
+ let description;
1296
+ let placeholder;
1297
+ let displayNameProvenance;
1298
+ let descriptionProvenance;
1299
+ let placeholderProvenance;
1134
1300
  const sourceFile = node.getSourceFile();
1135
1301
  const sourceText = sourceFile.getFullText();
1136
1302
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1150,9 +1316,37 @@ function parseTSDocTags(node, file = "") {
1150
1316
  const docComment = parserContext.docComment;
1151
1317
  for (const block of docComment.customBlocks) {
1152
1318
  const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
1319
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1320
+ const text2 = extractBlockText(block).trim();
1321
+ if (text2 === "") continue;
1322
+ const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1323
+ if (tagName === "displayName") {
1324
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1325
+ displayName = text2;
1326
+ displayNameProvenance = provenance2;
1327
+ }
1328
+ } else if (tagName === "format") {
1329
+ annotations.push({
1330
+ kind: "annotation",
1331
+ annotationKind: "format",
1332
+ value: text2,
1333
+ provenance: provenance2
1334
+ });
1335
+ } else {
1336
+ if (tagName === "description" && description === void 0) {
1337
+ description = text2;
1338
+ descriptionProvenance = provenance2;
1339
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1340
+ placeholder = text2;
1341
+ placeholderProvenance = provenance2;
1342
+ }
1343
+ }
1344
+ continue;
1345
+ }
1153
1346
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1154
1347
  const text = extractBlockText(block).trim();
1155
- if (text === "") continue;
1348
+ const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1349
+ if (text === "" && expectedType !== "boolean") continue;
1156
1350
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1157
1351
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1158
1352
  if (constraintNode) {
@@ -1160,14 +1354,47 @@ function parseTSDocTags(node, file = "") {
1160
1354
  }
1161
1355
  }
1162
1356
  if (docComment.deprecatedBlock !== void 0) {
1357
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1163
1358
  annotations.push({
1164
1359
  kind: "annotation",
1165
1360
  annotationKind: "deprecated",
1361
+ ...message !== "" && { message },
1166
1362
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1167
1363
  });
1168
1364
  }
1365
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1366
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1367
+ if (remarks !== "") {
1368
+ description = remarks;
1369
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1370
+ }
1371
+ }
1169
1372
  }
1170
1373
  }
1374
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1375
+ annotations.push({
1376
+ kind: "annotation",
1377
+ annotationKind: "displayName",
1378
+ value: displayName,
1379
+ provenance: displayNameProvenance
1380
+ });
1381
+ }
1382
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1383
+ annotations.push({
1384
+ kind: "annotation",
1385
+ annotationKind: "description",
1386
+ value: description,
1387
+ provenance: descriptionProvenance
1388
+ });
1389
+ }
1390
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1391
+ annotations.push({
1392
+ kind: "annotation",
1393
+ annotationKind: "placeholder",
1394
+ value: placeholder,
1395
+ provenance: placeholderProvenance
1396
+ });
1397
+ }
1171
1398
  const jsDocTagsAll = ts2.getJSDocTags(node);
1172
1399
  for (const tag of jsDocTagsAll) {
1173
1400
  const tagName = normalizeConstraintTagName(tag.tagName.text);
@@ -1176,47 +1403,39 @@ function parseTSDocTags(node, file = "") {
1176
1403
  if (commentText === void 0 || commentText.trim() === "") continue;
1177
1404
  const text = commentText.trim();
1178
1405
  const provenance = provenanceForJSDocTag(tag, file);
1406
+ if (tagName === "defaultValue") {
1407
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1408
+ annotations.push(defaultValueNode);
1409
+ continue;
1410
+ }
1179
1411
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1180
1412
  if (constraintNode) {
1181
1413
  constraints.push(constraintNode);
1182
1414
  }
1183
1415
  }
1416
+ return { constraints, annotations };
1417
+ }
1418
+ function extractDisplayNameMetadata(node) {
1184
1419
  let displayName;
1185
- let description;
1186
- let displayNameTag;
1187
- let descriptionTag;
1188
- for (const tag of jsDocTagsAll) {
1189
- const tagName = tag.tagName.text;
1420
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1421
+ for (const tag of ts2.getJSDocTags(node)) {
1422
+ const tagName = normalizeConstraintTagName(tag.tagName.text);
1423
+ if (tagName !== "displayName") continue;
1190
1424
  const commentText = getTagCommentText(tag);
1191
- if (commentText === void 0 || commentText.trim() === "") {
1425
+ if (commentText === void 0) continue;
1426
+ const text = commentText.trim();
1427
+ if (text === "") continue;
1428
+ const memberTarget = parseMemberTargetDisplayName(text);
1429
+ if (memberTarget) {
1430
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1192
1431
  continue;
1193
1432
  }
1194
- const trimmed = commentText.trim();
1195
- if (tagName === "Field_displayName") {
1196
- displayName = trimmed;
1197
- displayNameTag = tag;
1198
- } else if (tagName === "Field_description") {
1199
- description = trimmed;
1200
- descriptionTag = tag;
1201
- }
1433
+ displayName ??= text;
1202
1434
  }
1203
- if (displayName !== void 0 && displayNameTag) {
1204
- annotations.push({
1205
- kind: "annotation",
1206
- annotationKind: "displayName",
1207
- value: displayName,
1208
- provenance: provenanceForJSDocTag(displayNameTag, file)
1209
- });
1210
- }
1211
- if (description !== void 0 && descriptionTag) {
1212
- annotations.push({
1213
- kind: "annotation",
1214
- annotationKind: "description",
1215
- value: description,
1216
- provenance: provenanceForJSDocTag(descriptionTag, file)
1217
- });
1218
- }
1219
- return { constraints, annotations };
1435
+ return {
1436
+ ...displayName !== void 0 && { displayName },
1437
+ memberDisplayNames
1438
+ };
1220
1439
  }
1221
1440
  function extractPathTarget(text) {
1222
1441
  const trimmed = text.trimStart();
@@ -1280,7 +1499,45 @@ function parseConstraintValue(tagName, text, provenance) {
1280
1499
  }
1281
1500
  return null;
1282
1501
  }
1502
+ if (expectedType === "boolean") {
1503
+ const trimmed = effectiveText.trim();
1504
+ if (trimmed !== "" && trimmed !== "true") {
1505
+ return null;
1506
+ }
1507
+ if (tagName === "uniqueItems") {
1508
+ return {
1509
+ kind: "constraint",
1510
+ constraintKind: "uniqueItems",
1511
+ value: true,
1512
+ ...path3 && { path: path3 },
1513
+ provenance
1514
+ };
1515
+ }
1516
+ return null;
1517
+ }
1283
1518
  if (expectedType === "json") {
1519
+ if (tagName === "const") {
1520
+ const trimmedText = effectiveText.trim();
1521
+ if (trimmedText === "") return null;
1522
+ try {
1523
+ const parsed2 = JSON.parse(trimmedText);
1524
+ return {
1525
+ kind: "constraint",
1526
+ constraintKind: "const",
1527
+ value: parsed2,
1528
+ ...path3 && { path: path3 },
1529
+ provenance
1530
+ };
1531
+ } catch {
1532
+ return {
1533
+ kind: "constraint",
1534
+ constraintKind: "const",
1535
+ value: trimmedText,
1536
+ ...path3 && { path: path3 },
1537
+ provenance
1538
+ };
1539
+ }
1540
+ }
1284
1541
  const parsed = tryParseJson(effectiveText);
1285
1542
  if (!Array.isArray(parsed)) {
1286
1543
  return null;
@@ -1312,6 +1569,34 @@ function parseConstraintValue(tagName, text, provenance) {
1312
1569
  provenance
1313
1570
  };
1314
1571
  }
1572
+ function parseDefaultValueValue(text, provenance) {
1573
+ const trimmed = text.trim();
1574
+ let value;
1575
+ if (trimmed === "null") {
1576
+ value = null;
1577
+ } else if (trimmed === "true") {
1578
+ value = true;
1579
+ } else if (trimmed === "false") {
1580
+ value = false;
1581
+ } else {
1582
+ const parsed = tryParseJson(trimmed);
1583
+ value = parsed !== null ? parsed : trimmed;
1584
+ }
1585
+ return {
1586
+ kind: "annotation",
1587
+ annotationKind: "defaultValue",
1588
+ value,
1589
+ provenance
1590
+ };
1591
+ }
1592
+ function isMemberTargetDisplayName(text) {
1593
+ return parseMemberTargetDisplayName(text) !== null;
1594
+ }
1595
+ function parseMemberTargetDisplayName(text) {
1596
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1597
+ if (!match?.[1] || !match[2]) return null;
1598
+ return { target: match[1], label: match[2].trim() };
1599
+ }
1315
1600
  function provenanceForComment(range, sourceFile, file, tagName) {
1316
1601
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1317
1602
  return {
@@ -1393,11 +1678,17 @@ function isObjectType(type) {
1393
1678
  function isTypeReference(type) {
1394
1679
  return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1395
1680
  }
1681
+ var RESOLVING_TYPE_PLACEHOLDER = {
1682
+ kind: "object",
1683
+ properties: [],
1684
+ additionalProperties: true
1685
+ };
1396
1686
  function analyzeClassToIR(classDecl, checker, file = "") {
1397
1687
  const name = classDecl.name?.text ?? "AnonymousClass";
1398
1688
  const fields = [];
1399
1689
  const fieldLayouts = [];
1400
1690
  const typeRegistry = {};
1691
+ const annotations = extractJSDocAnnotationNodes(classDecl, file);
1401
1692
  const visiting = /* @__PURE__ */ new Set();
1402
1693
  const instanceMethods = [];
1403
1694
  const staticMethods = [];
@@ -1420,12 +1711,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1420
1711
  }
1421
1712
  }
1422
1713
  }
1423
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1714
+ return {
1715
+ name,
1716
+ fields,
1717
+ fieldLayouts,
1718
+ typeRegistry,
1719
+ ...annotations.length > 0 && { annotations },
1720
+ instanceMethods,
1721
+ staticMethods
1722
+ };
1424
1723
  }
1425
1724
  function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1426
1725
  const name = interfaceDecl.name.text;
1427
1726
  const fields = [];
1428
1727
  const typeRegistry = {};
1728
+ const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1429
1729
  const visiting = /* @__PURE__ */ new Set();
1430
1730
  for (const member of interfaceDecl.members) {
1431
1731
  if (ts4.isPropertySignature(member)) {
@@ -1436,7 +1736,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1436
1736
  }
1437
1737
  }
1438
1738
  const fieldLayouts = fields.map(() => ({}));
1439
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1739
+ return {
1740
+ name,
1741
+ fields,
1742
+ fieldLayouts,
1743
+ typeRegistry,
1744
+ ...annotations.length > 0 && { annotations },
1745
+ instanceMethods: [],
1746
+ staticMethods: []
1747
+ };
1440
1748
  }
1441
1749
  function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1442
1750
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
@@ -1451,6 +1759,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1451
1759
  const name = typeAlias.name.text;
1452
1760
  const fields = [];
1453
1761
  const typeRegistry = {};
1762
+ const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1454
1763
  const visiting = /* @__PURE__ */ new Set();
1455
1764
  for (const member of typeAlias.type.members) {
1456
1765
  if (ts4.isPropertySignature(member)) {
@@ -1467,6 +1776,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1467
1776
  fields,
1468
1777
  fieldLayouts: fields.map(() => ({})),
1469
1778
  typeRegistry,
1779
+ ...annotations.length > 0 && { annotations },
1470
1780
  instanceMethods: [],
1471
1781
  staticMethods: []
1472
1782
  }
@@ -1480,18 +1790,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1480
1790
  const tsType = checker.getTypeAtLocation(prop);
1481
1791
  const optional = prop.questionToken !== void 0;
1482
1792
  const provenance = provenanceForNode(prop, file);
1483
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1793
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1484
1794
  const constraints = [];
1485
1795
  if (prop.type) {
1486
1796
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1487
1797
  }
1488
1798
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1489
- const annotations = [];
1799
+ let annotations = [];
1490
1800
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1491
1801
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1492
- if (defaultAnnotation) {
1802
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1493
1803
  annotations.push(defaultAnnotation);
1494
1804
  }
1805
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1495
1806
  return {
1496
1807
  kind: "field",
1497
1808
  name,
@@ -1510,14 +1821,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1510
1821
  const tsType = checker.getTypeAtLocation(prop);
1511
1822
  const optional = prop.questionToken !== void 0;
1512
1823
  const provenance = provenanceForNode(prop, file);
1513
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1824
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1514
1825
  const constraints = [];
1515
1826
  if (prop.type) {
1516
1827
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1517
1828
  }
1518
1829
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1519
- const annotations = [];
1830
+ let annotations = [];
1520
1831
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1832
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1521
1833
  return {
1522
1834
  kind: "field",
1523
1835
  name,
@@ -1528,7 +1840,69 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1528
1840
  provenance
1529
1841
  };
1530
1842
  }
1531
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1843
+ function applyEnumMemberDisplayNames(type, annotations) {
1844
+ if (!annotations.some(
1845
+ (annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
1846
+ )) {
1847
+ return { type, annotations: [...annotations] };
1848
+ }
1849
+ const consumed = /* @__PURE__ */ new Set();
1850
+ const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
1851
+ if (consumed.size === 0) {
1852
+ return { type, annotations: [...annotations] };
1853
+ }
1854
+ return {
1855
+ type: nextType,
1856
+ annotations: annotations.filter((annotation) => !consumed.has(annotation))
1857
+ };
1858
+ }
1859
+ function rewriteEnumDisplayNames(type, annotations, consumed) {
1860
+ switch (type.kind) {
1861
+ case "enum":
1862
+ return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
1863
+ case "union": {
1864
+ return {
1865
+ ...type,
1866
+ members: type.members.map(
1867
+ (member) => rewriteEnumDisplayNames(member, annotations, consumed)
1868
+ )
1869
+ };
1870
+ }
1871
+ default:
1872
+ return type;
1873
+ }
1874
+ }
1875
+ function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
1876
+ const displayNames = /* @__PURE__ */ new Map();
1877
+ for (const annotation of annotations) {
1878
+ if (annotation.annotationKind !== "displayName") continue;
1879
+ const parsed = parseEnumMemberDisplayName(annotation.value);
1880
+ if (!parsed) continue;
1881
+ consumed.add(annotation);
1882
+ const member = type.members.find((m) => String(m.value) === parsed.value);
1883
+ if (!member) continue;
1884
+ displayNames.set(String(member.value), parsed.label);
1885
+ }
1886
+ if (displayNames.size === 0) {
1887
+ return type;
1888
+ }
1889
+ return {
1890
+ ...type,
1891
+ members: type.members.map((member) => {
1892
+ const displayName = displayNames.get(String(member.value));
1893
+ return displayName !== void 0 ? { ...member, displayName } : member;
1894
+ })
1895
+ };
1896
+ }
1897
+ function parseEnumMemberDisplayName(value) {
1898
+ const trimmed = value.trim();
1899
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
1900
+ if (!match?.[1] || !match[2]) return null;
1901
+ const label = match[2].trim();
1902
+ if (label === "") return null;
1903
+ return { value: match[1], label };
1904
+ }
1905
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1532
1906
  if (type.flags & ts4.TypeFlags.String) {
1533
1907
  return { kind: "primitive", primitiveKind: "string" };
1534
1908
  }
@@ -1557,7 +1931,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1557
1931
  };
1558
1932
  }
1559
1933
  if (type.isUnion()) {
1560
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
1934
+ return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1561
1935
  }
1562
1936
  if (checker.isArrayType(type)) {
1563
1937
  return resolveArrayType(type, checker, file, typeRegistry, visiting);
@@ -1567,70 +1941,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1567
1941
  }
1568
1942
  return { kind: "primitive", primitiveKind: "string" };
1569
1943
  }
1570
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
1944
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
1945
+ const typeName = getNamedTypeName(type);
1946
+ const namedDecl = getNamedTypeDeclaration(type);
1947
+ if (typeName && typeName in typeRegistry) {
1948
+ return { kind: "reference", name: typeName, typeArguments: [] };
1949
+ }
1571
1950
  const allTypes = type.types;
1572
1951
  const nonNullTypes = allTypes.filter(
1573
1952
  (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1574
1953
  );
1575
1954
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
1955
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1956
+ if (namedDecl) {
1957
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
1958
+ memberDisplayNames.set(value, label);
1959
+ }
1960
+ }
1961
+ if (sourceNode) {
1962
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
1963
+ memberDisplayNames.set(value, label);
1964
+ }
1965
+ }
1966
+ const registerNamed = (result) => {
1967
+ if (!typeName) {
1968
+ return result;
1969
+ }
1970
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
1971
+ typeRegistry[typeName] = {
1972
+ name: typeName,
1973
+ type: result,
1974
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
1975
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
1976
+ };
1977
+ return { kind: "reference", name: typeName, typeArguments: [] };
1978
+ };
1979
+ const applyMemberLabels = (members2) => members2.map((value) => {
1980
+ const displayName = memberDisplayNames.get(String(value));
1981
+ return displayName !== void 0 ? { value, displayName } : { value };
1982
+ });
1576
1983
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1577
1984
  if (isBooleanUnion2) {
1578
1985
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1579
- if (hasNull) {
1580
- return {
1581
- kind: "union",
1582
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1583
- };
1584
- }
1585
- return boolNode;
1986
+ const result = hasNull ? {
1987
+ kind: "union",
1988
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1989
+ } : boolNode;
1990
+ return registerNamed(result);
1586
1991
  }
1587
1992
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1588
1993
  if (allStringLiterals && nonNullTypes.length > 0) {
1589
1994
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1590
1995
  const enumNode = {
1591
1996
  kind: "enum",
1592
- members: stringTypes.map((t) => ({ value: t.value }))
1997
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1593
1998
  };
1594
- if (hasNull) {
1595
- return {
1596
- kind: "union",
1597
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1598
- };
1599
- }
1600
- return enumNode;
1999
+ const result = hasNull ? {
2000
+ kind: "union",
2001
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2002
+ } : enumNode;
2003
+ return registerNamed(result);
1601
2004
  }
1602
2005
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1603
2006
  if (allNumberLiterals && nonNullTypes.length > 0) {
1604
2007
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1605
2008
  const enumNode = {
1606
2009
  kind: "enum",
1607
- members: numberTypes.map((t) => ({ value: t.value }))
2010
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1608
2011
  };
1609
- if (hasNull) {
1610
- return {
1611
- kind: "union",
1612
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1613
- };
1614
- }
1615
- return enumNode;
2012
+ const result = hasNull ? {
2013
+ kind: "union",
2014
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2015
+ } : enumNode;
2016
+ return registerNamed(result);
1616
2017
  }
1617
2018
  if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1618
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1619
- if (hasNull) {
1620
- return {
1621
- kind: "union",
1622
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1623
- };
1624
- }
1625
- return inner;
2019
+ const inner = resolveTypeNode(
2020
+ nonNullTypes[0],
2021
+ checker,
2022
+ file,
2023
+ typeRegistry,
2024
+ visiting,
2025
+ sourceNode
2026
+ );
2027
+ const result = hasNull ? {
2028
+ kind: "union",
2029
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2030
+ } : inner;
2031
+ return registerNamed(result);
1626
2032
  }
1627
2033
  const members = nonNullTypes.map(
1628
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2034
+ (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1629
2035
  );
1630
2036
  if (hasNull) {
1631
2037
  members.push({ kind: "primitive", primitiveKind: "null" });
1632
2038
  }
1633
- return { kind: "union", members };
2039
+ return registerNamed({ kind: "union", members });
1634
2040
  }
1635
2041
  function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1636
2042
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
@@ -1638,15 +2044,92 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1638
2044
  const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1639
2045
  return { kind: "array", items };
1640
2046
  }
2047
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
2048
+ if (type.getProperties().length > 0) {
2049
+ return null;
2050
+ }
2051
+ const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
2052
+ if (!indexInfo) {
2053
+ return null;
2054
+ }
2055
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2056
+ return { kind: "record", valueType };
2057
+ }
2058
+ function typeNodeContainsReference(type, targetName) {
2059
+ switch (type.kind) {
2060
+ case "reference":
2061
+ return type.name === targetName;
2062
+ case "array":
2063
+ return typeNodeContainsReference(type.items, targetName);
2064
+ case "record":
2065
+ return typeNodeContainsReference(type.valueType, targetName);
2066
+ case "union":
2067
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2068
+ case "object":
2069
+ return type.properties.some(
2070
+ (property) => typeNodeContainsReference(property.type, targetName)
2071
+ );
2072
+ case "primitive":
2073
+ case "enum":
2074
+ case "dynamic":
2075
+ case "custom":
2076
+ return false;
2077
+ default: {
2078
+ const _exhaustive = type;
2079
+ return _exhaustive;
2080
+ }
2081
+ }
2082
+ }
1641
2083
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
2084
+ const typeName = getNamedTypeName(type);
2085
+ const namedTypeName = typeName ?? void 0;
2086
+ const namedDecl = getNamedTypeDeclaration(type);
2087
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2088
+ const clearNamedTypeRegistration = () => {
2089
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2090
+ return;
2091
+ }
2092
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2093
+ };
1642
2094
  if (visiting.has(type)) {
2095
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2096
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2097
+ }
1643
2098
  return { kind: "object", properties: [], additionalProperties: false };
1644
2099
  }
2100
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2101
+ typeRegistry[namedTypeName] = {
2102
+ name: namedTypeName,
2103
+ type: RESOLVING_TYPE_PLACEHOLDER,
2104
+ provenance: provenanceForDeclaration(namedDecl, file)
2105
+ };
2106
+ }
1645
2107
  visiting.add(type);
1646
- const typeName = getNamedTypeName(type);
1647
- if (typeName && typeName in typeRegistry) {
2108
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2109
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2110
+ visiting.delete(type);
2111
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2112
+ }
2113
+ }
2114
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2115
+ if (recordNode) {
1648
2116
  visiting.delete(type);
1649
- return { kind: "reference", name: typeName, typeArguments: [] };
2117
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2118
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2119
+ if (!isRecursiveRecord) {
2120
+ clearNamedTypeRegistration();
2121
+ return recordNode;
2122
+ }
2123
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2124
+ typeRegistry[namedTypeName] = {
2125
+ name: namedTypeName,
2126
+ type: recordNode,
2127
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2128
+ provenance: provenanceForDeclaration(namedDecl, file)
2129
+ };
2130
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2131
+ }
2132
+ return recordNode;
1650
2133
  }
1651
2134
  const properties = [];
1652
2135
  const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
@@ -1655,7 +2138,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1655
2138
  if (!declaration) continue;
1656
2139
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1657
2140
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1658
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2141
+ const propTypeNode = resolveTypeNode(
2142
+ propType,
2143
+ checker,
2144
+ file,
2145
+ typeRegistry,
2146
+ visiting,
2147
+ declaration
2148
+ );
1659
2149
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1660
2150
  properties.push({
1661
2151
  name: prop.name,
@@ -1670,15 +2160,17 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1670
2160
  const objectNode = {
1671
2161
  kind: "object",
1672
2162
  properties,
1673
- additionalProperties: false
2163
+ additionalProperties: true
1674
2164
  };
1675
- if (typeName) {
1676
- typeRegistry[typeName] = {
1677
- name: typeName,
2165
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2166
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2167
+ typeRegistry[namedTypeName] = {
2168
+ name: namedTypeName,
1678
2169
  type: objectNode,
1679
- provenance: provenanceForFile(file)
2170
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2171
+ provenance: provenanceForDeclaration(namedDecl, file)
1680
2172
  };
1681
- return { kind: "reference", name: typeName, typeArguments: [] };
2173
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1682
2174
  }
1683
2175
  return objectNode;
1684
2176
  }
@@ -1770,6 +2262,12 @@ function provenanceForNode(node, file) {
1770
2262
  function provenanceForFile(file) {
1771
2263
  return { surface: "tsdoc", file, line: 0, column: 0 };
1772
2264
  }
2265
+ function provenanceForDeclaration(node, file) {
2266
+ if (!node) {
2267
+ return provenanceForFile(file);
2268
+ }
2269
+ return provenanceForNode(node, file);
2270
+ }
1773
2271
  function getNamedTypeName(type) {
1774
2272
  const symbol = type.getSymbol();
1775
2273
  if (symbol?.declarations) {
@@ -1788,6 +2286,20 @@ function getNamedTypeName(type) {
1788
2286
  }
1789
2287
  return null;
1790
2288
  }
2289
+ function getNamedTypeDeclaration(type) {
2290
+ const symbol = type.getSymbol();
2291
+ if (symbol?.declarations) {
2292
+ const decl = symbol.declarations[0];
2293
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2294
+ return decl;
2295
+ }
2296
+ }
2297
+ const aliasSymbol = type.aliasSymbol;
2298
+ if (aliasSymbol?.declarations) {
2299
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2300
+ }
2301
+ return void 0;
2302
+ }
1791
2303
  function analyzeMethod(method, checker) {
1792
2304
  if (!ts4.isIdentifier(method.name)) {
1793
2305
  return null;
@@ -1872,16 +2384,219 @@ function generateSchemas(options) {
1872
2384
  );
1873
2385
  }
1874
2386
 
2387
+ // src/generators/mixed-authoring.ts
2388
+ function buildMixedAuthoringSchemas(options) {
2389
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2390
+ const analysis = analyzeNamedType(filePath, typeName);
2391
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2392
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2393
+ return {
2394
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2395
+ uiSchema: generateUiSchemaFromIR(ir)
2396
+ };
2397
+ }
2398
+ function analyzeNamedType(filePath, typeName) {
2399
+ const ctx = createProgramContext(filePath);
2400
+ const source = { file: filePath };
2401
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2402
+ if (classDecl !== null) {
2403
+ return analyzeClassToIR(classDecl, ctx.checker, source.file);
2404
+ }
2405
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2406
+ if (interfaceDecl !== null) {
2407
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2408
+ }
2409
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2410
+ if (typeAlias !== null) {
2411
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2412
+ if (result.ok) {
2413
+ return result.analysis;
2414
+ }
2415
+ throw new Error(result.error);
2416
+ }
2417
+ throw new Error(
2418
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2419
+ );
2420
+ }
2421
+ function composeAnalysisWithOverlays(analysis, overlays) {
2422
+ const overlayIR = canonicalizeChainDSL(overlays);
2423
+ const overlayFields = collectOverlayFields(overlayIR.elements);
2424
+ if (overlayFields.length === 0) {
2425
+ return analysis;
2426
+ }
2427
+ const overlayByName = /* @__PURE__ */ new Map();
2428
+ for (const field of overlayFields) {
2429
+ if (overlayByName.has(field.name)) {
2430
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
2431
+ }
2432
+ overlayByName.set(field.name, field);
2433
+ }
2434
+ const mergedFields = [];
2435
+ for (const baseField of analysis.fields) {
2436
+ const overlayField = overlayByName.get(baseField.name);
2437
+ if (overlayField === void 0) {
2438
+ mergedFields.push(baseField);
2439
+ continue;
2440
+ }
2441
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
2442
+ overlayByName.delete(baseField.name);
2443
+ }
2444
+ if (overlayByName.size > 0) {
2445
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
2446
+ throw new Error(
2447
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
2448
+ );
2449
+ }
2450
+ return {
2451
+ ...analysis,
2452
+ fields: mergedFields
2453
+ };
2454
+ }
2455
+ function collectOverlayFields(elements) {
2456
+ const fields = [];
2457
+ for (const element of elements) {
2458
+ switch (element.kind) {
2459
+ case "field":
2460
+ fields.push(element);
2461
+ break;
2462
+ case "group":
2463
+ fields.push(...collectOverlayFields(element.elements));
2464
+ break;
2465
+ case "conditional":
2466
+ fields.push(...collectOverlayFields(element.elements));
2467
+ break;
2468
+ default: {
2469
+ const _exhaustive = element;
2470
+ void _exhaustive;
2471
+ }
2472
+ }
2473
+ }
2474
+ return fields;
2475
+ }
2476
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
2477
+ assertSupportedOverlayField(baseField, overlayField);
2478
+ return {
2479
+ ...baseField,
2480
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
2481
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
2482
+ };
2483
+ }
2484
+ function assertSupportedOverlayField(baseField, overlayField) {
2485
+ if (overlayField.constraints.length > 0) {
2486
+ throw new Error(
2487
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2488
+ );
2489
+ }
2490
+ if (overlayField.required) {
2491
+ throw new Error(
2492
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2493
+ );
2494
+ }
2495
+ }
2496
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
2497
+ const { type: baseType } = baseField;
2498
+ const { type: overlayType } = overlayField;
2499
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
2500
+ throw new Error(
2501
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
2502
+ );
2503
+ }
2504
+ if (overlayType.kind === "dynamic") {
2505
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
2506
+ throw new Error(
2507
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
2508
+ );
2509
+ }
2510
+ return overlayType;
2511
+ }
2512
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
2513
+ throw new Error(
2514
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
2515
+ );
2516
+ }
2517
+ return baseType;
2518
+ }
2519
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
2520
+ const overlayType = overlayField.type;
2521
+ if (overlayType.kind !== "dynamic") {
2522
+ return false;
2523
+ }
2524
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
2525
+ if (resolvedBaseType === null) {
2526
+ return false;
2527
+ }
2528
+ if (overlayType.dynamicKind === "enum") {
2529
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
2530
+ }
2531
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
2532
+ }
2533
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
2534
+ if (type.kind !== "reference") {
2535
+ return type;
2536
+ }
2537
+ if (seen.has(type.name)) {
2538
+ return null;
2539
+ }
2540
+ const definition = typeRegistry[type.name];
2541
+ if (definition === void 0) {
2542
+ return null;
2543
+ }
2544
+ seen.add(type.name);
2545
+ return resolveReferenceType(definition.type, typeRegistry, seen);
2546
+ }
2547
+ function isSameStaticTypeShape(baseType, overlayType) {
2548
+ if (baseType.kind !== overlayType.kind) {
2549
+ return false;
2550
+ }
2551
+ switch (baseType.kind) {
2552
+ case "primitive":
2553
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
2554
+ case "enum":
2555
+ return overlayType.kind === "enum";
2556
+ case "dynamic":
2557
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
2558
+ case "record":
2559
+ return overlayType.kind === "record";
2560
+ case "reference":
2561
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
2562
+ case "union":
2563
+ return overlayType.kind === "union";
2564
+ case "custom":
2565
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
2566
+ case "object":
2567
+ case "array":
2568
+ return true;
2569
+ default: {
2570
+ const _exhaustive = baseType;
2571
+ return _exhaustive;
2572
+ }
2573
+ }
2574
+ }
2575
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2576
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
2577
+ const overlayOnly = overlayAnnotations.filter(
2578
+ (annotation) => !baseKeys.has(annotationKey(annotation))
2579
+ );
2580
+ return [...overlayOnly, ...baseAnnotations];
2581
+ }
2582
+ function annotationKey(annotation) {
2583
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
2584
+ }
2585
+
1875
2586
  // src/index.ts
1876
- function buildFormSchemas(form) {
2587
+ function buildFormSchemas(form, options) {
1877
2588
  return {
1878
- jsonSchema: generateJsonSchema(form),
2589
+ jsonSchema: generateJsonSchema(form, options),
1879
2590
  uiSchema: generateUiSchema(form)
1880
2591
  };
1881
2592
  }
1882
2593
  function writeSchemas(form, options) {
1883
- const { outDir, name = "schema", indent = 2 } = options;
1884
- const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
2594
+ const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
2595
+ const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
2596
+ extensionRegistry,
2597
+ vendorPrefix
2598
+ };
2599
+ const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
1885
2600
  if (!fs.existsSync(outDir)) {
1886
2601
  fs.mkdirSync(outDir, { recursive: true });
1887
2602
  }
@@ -1893,10 +2608,13 @@ function writeSchemas(form, options) {
1893
2608
  }
1894
2609
  export {
1895
2610
  buildFormSchemas,
2611
+ buildMixedAuthoringSchemas,
1896
2612
  categorizationSchema,
1897
2613
  categorySchema,
1898
2614
  controlSchema,
2615
+ createExtensionRegistry,
1899
2616
  generateJsonSchema,
2617
+ generateJsonSchemaFromIR,
1900
2618
  generateSchemas,
1901
2619
  generateSchemasFromClass,
1902
2620
  generateUiSchema,