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