@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.
- package/dist/__tests__/extension-runtime.integration.test.d.ts +2 -0
- package/dist/__tests__/extension-runtime.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts +22 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +6 -6
- package/dist/__tests__/fixtures/example-interface-types.d.ts +26 -26
- package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -1
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +30 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
- package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
- package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts +17 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +11 -4
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +5 -3
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +7 -51
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +25 -9
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +546 -102
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +15 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +544 -102
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +170 -6
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +877 -128
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +876 -131
- package/dist/cli.js.map +1 -1
- package/dist/generators/mixed-authoring.d.ts +45 -0
- package/dist/generators/mixed-authoring.d.ts.map +1 -0
- package/dist/index.cjs +850 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +847 -129
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +946 -187
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +944 -189
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/generator.d.ts +8 -2
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +27 -4
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/json-schema/types.d.ts +1 -1
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts +3 -7
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -10
- 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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
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) =>
|
|
1997
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1593
1998
|
};
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
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) =>
|
|
2010
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1608
2011
|
};
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
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(
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
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
|
-
|
|
1647
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
2163
|
+
additionalProperties: true
|
|
1674
2164
|
};
|
|
1675
|
-
if (
|
|
1676
|
-
|
|
1677
|
-
|
|
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
|
-
|
|
2170
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2171
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1680
2172
|
};
|
|
1681
|
-
return { kind: "reference", name:
|
|
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
|
|
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,
|