@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.js
CHANGED
|
@@ -182,7 +182,7 @@ function canonicalizeArrayField(field) {
|
|
|
182
182
|
const itemsType = {
|
|
183
183
|
kind: "object",
|
|
184
184
|
properties: itemProperties,
|
|
185
|
-
additionalProperties:
|
|
185
|
+
additionalProperties: true
|
|
186
186
|
};
|
|
187
187
|
const type = { kind: "array", items: itemsType };
|
|
188
188
|
const constraints = [];
|
|
@@ -217,7 +217,7 @@ function canonicalizeObjectField(field) {
|
|
|
217
217
|
const type = {
|
|
218
218
|
kind: "object",
|
|
219
219
|
properties,
|
|
220
|
-
additionalProperties:
|
|
220
|
+
additionalProperties: true
|
|
221
221
|
};
|
|
222
222
|
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
223
223
|
}
|
|
@@ -342,6 +342,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
342
342
|
irVersion: IR_VERSION2,
|
|
343
343
|
elements,
|
|
344
344
|
typeRegistry: analysis.typeRegistry,
|
|
345
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
345
346
|
provenance
|
|
346
347
|
};
|
|
347
348
|
}
|
|
@@ -415,13 +416,26 @@ var init_canonicalize = __esm({
|
|
|
415
416
|
});
|
|
416
417
|
|
|
417
418
|
// src/json-schema/ir-generator.ts
|
|
418
|
-
function makeContext() {
|
|
419
|
-
|
|
419
|
+
function makeContext(options) {
|
|
420
|
+
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
421
|
+
if (!vendorPrefix.startsWith("x-")) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
defs: {},
|
|
428
|
+
extensionRegistry: options?.extensionRegistry,
|
|
429
|
+
vendorPrefix
|
|
430
|
+
};
|
|
420
431
|
}
|
|
421
|
-
function generateJsonSchemaFromIR(ir) {
|
|
422
|
-
const ctx = makeContext();
|
|
432
|
+
function generateJsonSchemaFromIR(ir, options) {
|
|
433
|
+
const ctx = makeContext(options);
|
|
423
434
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
424
435
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
436
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
437
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
438
|
+
}
|
|
425
439
|
}
|
|
426
440
|
const properties = {};
|
|
427
441
|
const required = [];
|
|
@@ -433,6 +447,9 @@ function generateJsonSchemaFromIR(ir) {
|
|
|
433
447
|
properties,
|
|
434
448
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
435
449
|
};
|
|
450
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
451
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
452
|
+
}
|
|
436
453
|
if (Object.keys(ctx.defs).length > 0) {
|
|
437
454
|
result.$defs = ctx.defs;
|
|
438
455
|
}
|
|
@@ -462,25 +479,54 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
462
479
|
}
|
|
463
480
|
function generateFieldSchema(field, ctx) {
|
|
464
481
|
const schema = generateTypeNode(field.type, ctx);
|
|
482
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
465
483
|
const directConstraints = [];
|
|
484
|
+
const itemConstraints = [];
|
|
466
485
|
const pathConstraints = [];
|
|
467
486
|
for (const c of field.constraints) {
|
|
468
487
|
if (c.path) {
|
|
469
488
|
pathConstraints.push(c);
|
|
489
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
490
|
+
itemConstraints.push(c);
|
|
470
491
|
} else {
|
|
471
492
|
directConstraints.push(c);
|
|
472
493
|
}
|
|
473
494
|
}
|
|
474
|
-
applyConstraints(schema, directConstraints);
|
|
475
|
-
|
|
495
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
496
|
+
if (itemStringSchema !== void 0) {
|
|
497
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
498
|
+
}
|
|
499
|
+
const rootAnnotations = [];
|
|
500
|
+
const itemAnnotations = [];
|
|
501
|
+
for (const annotation of field.annotations) {
|
|
502
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
503
|
+
itemAnnotations.push(annotation);
|
|
504
|
+
} else {
|
|
505
|
+
rootAnnotations.push(annotation);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
509
|
+
if (itemStringSchema !== void 0) {
|
|
510
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
511
|
+
}
|
|
476
512
|
if (pathConstraints.length === 0) {
|
|
477
513
|
return schema;
|
|
478
514
|
}
|
|
479
|
-
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
515
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
480
516
|
}
|
|
481
|
-
function
|
|
517
|
+
function isStringItemConstraint(constraint) {
|
|
518
|
+
switch (constraint.constraintKind) {
|
|
519
|
+
case "minLength":
|
|
520
|
+
case "maxLength":
|
|
521
|
+
case "pattern":
|
|
522
|
+
return true;
|
|
523
|
+
default:
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
482
528
|
if (schema.type === "array" && schema.items) {
|
|
483
|
-
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
529
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
484
530
|
return schema;
|
|
485
531
|
}
|
|
486
532
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -494,7 +540,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
|
494
540
|
const propertyOverrides = {};
|
|
495
541
|
for (const [target, constraints] of byTarget) {
|
|
496
542
|
const subSchema = {};
|
|
497
|
-
applyConstraints(subSchema, constraints);
|
|
543
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
498
544
|
propertyOverrides[target] = subSchema;
|
|
499
545
|
}
|
|
500
546
|
if (schema.$ref) {
|
|
@@ -538,6 +584,8 @@ function generateTypeNode(type, ctx) {
|
|
|
538
584
|
return generateArrayType(type, ctx);
|
|
539
585
|
case "object":
|
|
540
586
|
return generateObjectType(type, ctx);
|
|
587
|
+
case "record":
|
|
588
|
+
return generateRecordType(type, ctx);
|
|
541
589
|
case "union":
|
|
542
590
|
return generateUnionType(type, ctx);
|
|
543
591
|
case "reference":
|
|
@@ -545,7 +593,7 @@ function generateTypeNode(type, ctx) {
|
|
|
545
593
|
case "dynamic":
|
|
546
594
|
return generateDynamicType(type);
|
|
547
595
|
case "custom":
|
|
548
|
-
return generateCustomType(type);
|
|
596
|
+
return generateCustomType(type, ctx);
|
|
549
597
|
default: {
|
|
550
598
|
const _exhaustive = type;
|
|
551
599
|
return _exhaustive;
|
|
@@ -594,16 +642,27 @@ function generateObjectType(type, ctx) {
|
|
|
594
642
|
}
|
|
595
643
|
return schema;
|
|
596
644
|
}
|
|
645
|
+
function generateRecordType(type, ctx) {
|
|
646
|
+
return {
|
|
647
|
+
type: "object",
|
|
648
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
649
|
+
};
|
|
650
|
+
}
|
|
597
651
|
function generatePropertySchema(prop, ctx) {
|
|
598
652
|
const schema = generateTypeNode(prop.type, ctx);
|
|
599
|
-
applyConstraints(schema, prop.constraints);
|
|
600
|
-
applyAnnotations(schema, prop.annotations);
|
|
653
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
654
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
601
655
|
return schema;
|
|
602
656
|
}
|
|
603
657
|
function generateUnionType(type, ctx) {
|
|
604
658
|
if (isBooleanUnion(type)) {
|
|
605
659
|
return { type: "boolean" };
|
|
606
660
|
}
|
|
661
|
+
if (isNullableUnion(type)) {
|
|
662
|
+
return {
|
|
663
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
664
|
+
};
|
|
665
|
+
}
|
|
607
666
|
return {
|
|
608
667
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
609
668
|
};
|
|
@@ -613,6 +672,13 @@ function isBooleanUnion(type) {
|
|
|
613
672
|
const kinds = type.members.map((m) => m.kind);
|
|
614
673
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
615
674
|
}
|
|
675
|
+
function isNullableUnion(type) {
|
|
676
|
+
if (type.members.length !== 2) return false;
|
|
677
|
+
const nullCount = type.members.filter(
|
|
678
|
+
(m) => m.kind === "primitive" && m.primitiveKind === "null"
|
|
679
|
+
).length;
|
|
680
|
+
return nullCount === 1;
|
|
681
|
+
}
|
|
616
682
|
function generateReferenceType(type) {
|
|
617
683
|
return { $ref: `#/$defs/${type.name}` };
|
|
618
684
|
}
|
|
@@ -633,10 +699,7 @@ function generateDynamicType(type) {
|
|
|
633
699
|
"x-formspec-schemaSource": type.sourceKey
|
|
634
700
|
};
|
|
635
701
|
}
|
|
636
|
-
function
|
|
637
|
-
return { type: "object" };
|
|
638
|
-
}
|
|
639
|
-
function applyConstraints(schema, constraints) {
|
|
702
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
640
703
|
for (const constraint of constraints) {
|
|
641
704
|
switch (constraint.constraintKind) {
|
|
642
705
|
case "minimum":
|
|
@@ -678,9 +741,13 @@ function applyConstraints(schema, constraints) {
|
|
|
678
741
|
case "uniqueItems":
|
|
679
742
|
schema.uniqueItems = constraint.value;
|
|
680
743
|
break;
|
|
744
|
+
case "const":
|
|
745
|
+
schema.const = constraint.value;
|
|
746
|
+
break;
|
|
681
747
|
case "allowedMembers":
|
|
682
748
|
break;
|
|
683
749
|
case "custom":
|
|
750
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
684
751
|
break;
|
|
685
752
|
default: {
|
|
686
753
|
const _exhaustive = constraint;
|
|
@@ -689,7 +756,7 @@ function applyConstraints(schema, constraints) {
|
|
|
689
756
|
}
|
|
690
757
|
}
|
|
691
758
|
}
|
|
692
|
-
function applyAnnotations(schema, annotations) {
|
|
759
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
693
760
|
for (const annotation of annotations) {
|
|
694
761
|
switch (annotation.annotationKind) {
|
|
695
762
|
case "displayName":
|
|
@@ -701,14 +768,21 @@ function applyAnnotations(schema, annotations) {
|
|
|
701
768
|
case "defaultValue":
|
|
702
769
|
schema.default = annotation.value;
|
|
703
770
|
break;
|
|
771
|
+
case "format":
|
|
772
|
+
schema.format = annotation.value;
|
|
773
|
+
break;
|
|
704
774
|
case "deprecated":
|
|
705
775
|
schema.deprecated = true;
|
|
776
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
777
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
778
|
+
}
|
|
706
779
|
break;
|
|
707
780
|
case "placeholder":
|
|
708
781
|
break;
|
|
709
782
|
case "formatHint":
|
|
710
783
|
break;
|
|
711
784
|
case "custom":
|
|
785
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
712
786
|
break;
|
|
713
787
|
default: {
|
|
714
788
|
const _exhaustive = annotation;
|
|
@@ -717,6 +791,36 @@ function applyAnnotations(schema, annotations) {
|
|
|
717
791
|
}
|
|
718
792
|
}
|
|
719
793
|
}
|
|
794
|
+
function generateCustomType(type, ctx) {
|
|
795
|
+
const registration = ctx.extensionRegistry?.findType(type.typeId);
|
|
796
|
+
if (registration === void 0) {
|
|
797
|
+
throw new Error(
|
|
798
|
+
`Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
|
|
802
|
+
}
|
|
803
|
+
function applyCustomConstraint(schema, constraint, ctx) {
|
|
804
|
+
const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
|
|
805
|
+
if (registration === void 0) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
|
|
811
|
+
}
|
|
812
|
+
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
813
|
+
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
814
|
+
if (registration === void 0) {
|
|
815
|
+
throw new Error(
|
|
816
|
+
`Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
if (registration.toJsonSchema === void 0) {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
|
|
823
|
+
}
|
|
720
824
|
var init_ir_generator = __esm({
|
|
721
825
|
"src/json-schema/ir-generator.ts"() {
|
|
722
826
|
"use strict";
|
|
@@ -724,9 +828,9 @@ var init_ir_generator = __esm({
|
|
|
724
828
|
});
|
|
725
829
|
|
|
726
830
|
// src/json-schema/generator.ts
|
|
727
|
-
function generateJsonSchema(form) {
|
|
831
|
+
function generateJsonSchema(form, options) {
|
|
728
832
|
const ir = canonicalizeChainDSL(form);
|
|
729
|
-
return generateJsonSchemaFromIR(ir);
|
|
833
|
+
return generateJsonSchemaFromIR(ir, options);
|
|
730
834
|
}
|
|
731
835
|
var init_generator = __esm({
|
|
732
836
|
"src/json-schema/generator.ts"() {
|
|
@@ -877,25 +981,31 @@ function createShowRule(fieldName, value) {
|
|
|
877
981
|
}
|
|
878
982
|
};
|
|
879
983
|
}
|
|
984
|
+
function flattenConditionSchema(scope, schema) {
|
|
985
|
+
if (schema.allOf === void 0) {
|
|
986
|
+
if (scope === "#") {
|
|
987
|
+
return [schema];
|
|
988
|
+
}
|
|
989
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
990
|
+
return [
|
|
991
|
+
{
|
|
992
|
+
properties: {
|
|
993
|
+
[fieldName]: schema
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
];
|
|
997
|
+
}
|
|
998
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
999
|
+
}
|
|
880
1000
|
function combineRules(parentRule, childRule) {
|
|
881
|
-
const parentCondition = parentRule.condition;
|
|
882
|
-
const childCondition = childRule.condition;
|
|
883
1001
|
return {
|
|
884
1002
|
effect: "SHOW",
|
|
885
1003
|
condition: {
|
|
886
1004
|
scope: "#",
|
|
887
1005
|
schema: {
|
|
888
1006
|
allOf: [
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
892
|
-
}
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
properties: {
|
|
896
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
897
|
-
}
|
|
898
|
-
}
|
|
1007
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
1008
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
899
1009
|
]
|
|
900
1010
|
}
|
|
901
1011
|
}
|
|
@@ -903,10 +1013,14 @@ function combineRules(parentRule, childRule) {
|
|
|
903
1013
|
}
|
|
904
1014
|
function fieldNodeToControl(field, parentRule) {
|
|
905
1015
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
1016
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
906
1017
|
const control = {
|
|
907
1018
|
type: "Control",
|
|
908
1019
|
scope: fieldToScope(field.name),
|
|
909
1020
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
1021
|
+
...placeholderAnnotation !== void 0 && {
|
|
1022
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
1023
|
+
},
|
|
910
1024
|
...parentRule !== void 0 && { rule: parentRule }
|
|
911
1025
|
};
|
|
912
1026
|
return control;
|
|
@@ -987,6 +1101,61 @@ var init_types = __esm({
|
|
|
987
1101
|
}
|
|
988
1102
|
});
|
|
989
1103
|
|
|
1104
|
+
// src/extensions/registry.ts
|
|
1105
|
+
function createExtensionRegistry(extensions) {
|
|
1106
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1107
|
+
const constraintMap = /* @__PURE__ */ new Map();
|
|
1108
|
+
const annotationMap = /* @__PURE__ */ new Map();
|
|
1109
|
+
for (const ext of extensions) {
|
|
1110
|
+
if (ext.types !== void 0) {
|
|
1111
|
+
for (const type of ext.types) {
|
|
1112
|
+
const qualifiedId = `${ext.extensionId}/${type.typeName}`;
|
|
1113
|
+
if (typeMap.has(qualifiedId)) {
|
|
1114
|
+
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1115
|
+
}
|
|
1116
|
+
typeMap.set(qualifiedId, type);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
if (ext.constraints !== void 0) {
|
|
1120
|
+
for (const constraint of ext.constraints) {
|
|
1121
|
+
const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
|
|
1122
|
+
if (constraintMap.has(qualifiedId)) {
|
|
1123
|
+
throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
|
|
1124
|
+
}
|
|
1125
|
+
constraintMap.set(qualifiedId, constraint);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (ext.annotations !== void 0) {
|
|
1129
|
+
for (const annotation of ext.annotations) {
|
|
1130
|
+
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
1131
|
+
if (annotationMap.has(qualifiedId)) {
|
|
1132
|
+
throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
|
|
1133
|
+
}
|
|
1134
|
+
annotationMap.set(qualifiedId, annotation);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return {
|
|
1139
|
+
extensions,
|
|
1140
|
+
findType: (typeId) => typeMap.get(typeId),
|
|
1141
|
+
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1142
|
+
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
var init_registry = __esm({
|
|
1146
|
+
"src/extensions/registry.ts"() {
|
|
1147
|
+
"use strict";
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
// src/extensions/index.ts
|
|
1152
|
+
var init_extensions = __esm({
|
|
1153
|
+
"src/extensions/index.ts"() {
|
|
1154
|
+
"use strict";
|
|
1155
|
+
init_registry();
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
|
|
990
1159
|
// src/json-schema/schema.ts
|
|
991
1160
|
import { z as z3 } from "zod";
|
|
992
1161
|
var jsonSchemaTypeSchema, jsonSchema7Schema;
|
|
@@ -1173,6 +1342,15 @@ function createFormSpecTSDocConfig() {
|
|
|
1173
1342
|
})
|
|
1174
1343
|
);
|
|
1175
1344
|
}
|
|
1345
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
1346
|
+
config.addTagDefinition(
|
|
1347
|
+
new TSDocTagDefinition({
|
|
1348
|
+
tagName: "@" + tagName,
|
|
1349
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
1350
|
+
allowMultiple: true
|
|
1351
|
+
})
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1176
1354
|
return config;
|
|
1177
1355
|
}
|
|
1178
1356
|
function getParser() {
|
|
@@ -1182,6 +1360,12 @@ function getParser() {
|
|
|
1182
1360
|
function parseTSDocTags(node, file = "") {
|
|
1183
1361
|
const constraints = [];
|
|
1184
1362
|
const annotations = [];
|
|
1363
|
+
let displayName;
|
|
1364
|
+
let description;
|
|
1365
|
+
let placeholder;
|
|
1366
|
+
let displayNameProvenance;
|
|
1367
|
+
let descriptionProvenance;
|
|
1368
|
+
let placeholderProvenance;
|
|
1185
1369
|
const sourceFile = node.getSourceFile();
|
|
1186
1370
|
const sourceText = sourceFile.getFullText();
|
|
1187
1371
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -1201,9 +1385,37 @@ function parseTSDocTags(node, file = "") {
|
|
|
1201
1385
|
const docComment = parserContext.docComment;
|
|
1202
1386
|
for (const block of docComment.customBlocks) {
|
|
1203
1387
|
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1388
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1389
|
+
const text2 = extractBlockText(block).trim();
|
|
1390
|
+
if (text2 === "") continue;
|
|
1391
|
+
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1392
|
+
if (tagName === "displayName") {
|
|
1393
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1394
|
+
displayName = text2;
|
|
1395
|
+
displayNameProvenance = provenance2;
|
|
1396
|
+
}
|
|
1397
|
+
} else if (tagName === "format") {
|
|
1398
|
+
annotations.push({
|
|
1399
|
+
kind: "annotation",
|
|
1400
|
+
annotationKind: "format",
|
|
1401
|
+
value: text2,
|
|
1402
|
+
provenance: provenance2
|
|
1403
|
+
});
|
|
1404
|
+
} else {
|
|
1405
|
+
if (tagName === "description" && description === void 0) {
|
|
1406
|
+
description = text2;
|
|
1407
|
+
descriptionProvenance = provenance2;
|
|
1408
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
1409
|
+
placeholder = text2;
|
|
1410
|
+
placeholderProvenance = provenance2;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1204
1415
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1205
1416
|
const text = extractBlockText(block).trim();
|
|
1206
|
-
|
|
1417
|
+
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1418
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
1207
1419
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1208
1420
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1209
1421
|
if (constraintNode) {
|
|
@@ -1211,14 +1423,47 @@ function parseTSDocTags(node, file = "") {
|
|
|
1211
1423
|
}
|
|
1212
1424
|
}
|
|
1213
1425
|
if (docComment.deprecatedBlock !== void 0) {
|
|
1426
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
1214
1427
|
annotations.push({
|
|
1215
1428
|
kind: "annotation",
|
|
1216
1429
|
annotationKind: "deprecated",
|
|
1430
|
+
...message !== "" && { message },
|
|
1217
1431
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1218
1432
|
});
|
|
1219
1433
|
}
|
|
1434
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
1435
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
1436
|
+
if (remarks !== "") {
|
|
1437
|
+
description = remarks;
|
|
1438
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1220
1441
|
}
|
|
1221
1442
|
}
|
|
1443
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
1444
|
+
annotations.push({
|
|
1445
|
+
kind: "annotation",
|
|
1446
|
+
annotationKind: "displayName",
|
|
1447
|
+
value: displayName,
|
|
1448
|
+
provenance: displayNameProvenance
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
1452
|
+
annotations.push({
|
|
1453
|
+
kind: "annotation",
|
|
1454
|
+
annotationKind: "description",
|
|
1455
|
+
value: description,
|
|
1456
|
+
provenance: descriptionProvenance
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
1460
|
+
annotations.push({
|
|
1461
|
+
kind: "annotation",
|
|
1462
|
+
annotationKind: "placeholder",
|
|
1463
|
+
value: placeholder,
|
|
1464
|
+
provenance: placeholderProvenance
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1222
1467
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1223
1468
|
for (const tag of jsDocTagsAll) {
|
|
1224
1469
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
@@ -1227,47 +1472,39 @@ function parseTSDocTags(node, file = "") {
|
|
|
1227
1472
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
1228
1473
|
const text = commentText.trim();
|
|
1229
1474
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
1475
|
+
if (tagName === "defaultValue") {
|
|
1476
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
1477
|
+
annotations.push(defaultValueNode);
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1230
1480
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1231
1481
|
if (constraintNode) {
|
|
1232
1482
|
constraints.push(constraintNode);
|
|
1233
1483
|
}
|
|
1234
1484
|
}
|
|
1485
|
+
return { constraints, annotations };
|
|
1486
|
+
}
|
|
1487
|
+
function extractDisplayNameMetadata(node) {
|
|
1235
1488
|
let displayName;
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
const tagName = tag.tagName.text;
|
|
1489
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1490
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
1491
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1492
|
+
if (tagName !== "displayName") continue;
|
|
1241
1493
|
const commentText = getTagCommentText(tag);
|
|
1242
|
-
if (commentText === void 0
|
|
1494
|
+
if (commentText === void 0) continue;
|
|
1495
|
+
const text = commentText.trim();
|
|
1496
|
+
if (text === "") continue;
|
|
1497
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
1498
|
+
if (memberTarget) {
|
|
1499
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
1243
1500
|
continue;
|
|
1244
1501
|
}
|
|
1245
|
-
|
|
1246
|
-
if (tagName === "Field_displayName") {
|
|
1247
|
-
displayName = trimmed;
|
|
1248
|
-
displayNameTag = tag;
|
|
1249
|
-
} else if (tagName === "Field_description") {
|
|
1250
|
-
description = trimmed;
|
|
1251
|
-
descriptionTag = tag;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
if (displayName !== void 0 && displayNameTag) {
|
|
1255
|
-
annotations.push({
|
|
1256
|
-
kind: "annotation",
|
|
1257
|
-
annotationKind: "displayName",
|
|
1258
|
-
value: displayName,
|
|
1259
|
-
provenance: provenanceForJSDocTag(displayNameTag, file)
|
|
1260
|
-
});
|
|
1502
|
+
displayName ??= text;
|
|
1261
1503
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
value: description,
|
|
1267
|
-
provenance: provenanceForJSDocTag(descriptionTag, file)
|
|
1268
|
-
});
|
|
1269
|
-
}
|
|
1270
|
-
return { constraints, annotations };
|
|
1504
|
+
return {
|
|
1505
|
+
...displayName !== void 0 && { displayName },
|
|
1506
|
+
memberDisplayNames
|
|
1507
|
+
};
|
|
1271
1508
|
}
|
|
1272
1509
|
function extractPathTarget(text) {
|
|
1273
1510
|
const trimmed = text.trimStart();
|
|
@@ -1331,7 +1568,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1331
1568
|
}
|
|
1332
1569
|
return null;
|
|
1333
1570
|
}
|
|
1571
|
+
if (expectedType === "boolean") {
|
|
1572
|
+
const trimmed = effectiveText.trim();
|
|
1573
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
1574
|
+
return null;
|
|
1575
|
+
}
|
|
1576
|
+
if (tagName === "uniqueItems") {
|
|
1577
|
+
return {
|
|
1578
|
+
kind: "constraint",
|
|
1579
|
+
constraintKind: "uniqueItems",
|
|
1580
|
+
value: true,
|
|
1581
|
+
...path4 && { path: path4 },
|
|
1582
|
+
provenance
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
return null;
|
|
1586
|
+
}
|
|
1334
1587
|
if (expectedType === "json") {
|
|
1588
|
+
if (tagName === "const") {
|
|
1589
|
+
const trimmedText = effectiveText.trim();
|
|
1590
|
+
if (trimmedText === "") return null;
|
|
1591
|
+
try {
|
|
1592
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
1593
|
+
return {
|
|
1594
|
+
kind: "constraint",
|
|
1595
|
+
constraintKind: "const",
|
|
1596
|
+
value: parsed2,
|
|
1597
|
+
...path4 && { path: path4 },
|
|
1598
|
+
provenance
|
|
1599
|
+
};
|
|
1600
|
+
} catch {
|
|
1601
|
+
return {
|
|
1602
|
+
kind: "constraint",
|
|
1603
|
+
constraintKind: "const",
|
|
1604
|
+
value: trimmedText,
|
|
1605
|
+
...path4 && { path: path4 },
|
|
1606
|
+
provenance
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1335
1610
|
const parsed = tryParseJson(effectiveText);
|
|
1336
1611
|
if (!Array.isArray(parsed)) {
|
|
1337
1612
|
return null;
|
|
@@ -1363,6 +1638,34 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1363
1638
|
provenance
|
|
1364
1639
|
};
|
|
1365
1640
|
}
|
|
1641
|
+
function parseDefaultValueValue(text, provenance) {
|
|
1642
|
+
const trimmed = text.trim();
|
|
1643
|
+
let value;
|
|
1644
|
+
if (trimmed === "null") {
|
|
1645
|
+
value = null;
|
|
1646
|
+
} else if (trimmed === "true") {
|
|
1647
|
+
value = true;
|
|
1648
|
+
} else if (trimmed === "false") {
|
|
1649
|
+
value = false;
|
|
1650
|
+
} else {
|
|
1651
|
+
const parsed = tryParseJson(trimmed);
|
|
1652
|
+
value = parsed !== null ? parsed : trimmed;
|
|
1653
|
+
}
|
|
1654
|
+
return {
|
|
1655
|
+
kind: "annotation",
|
|
1656
|
+
annotationKind: "defaultValue",
|
|
1657
|
+
value,
|
|
1658
|
+
provenance
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
function isMemberTargetDisplayName(text) {
|
|
1662
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
1663
|
+
}
|
|
1664
|
+
function parseMemberTargetDisplayName(text) {
|
|
1665
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1666
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1667
|
+
return { target: match[1], label: match[2].trim() };
|
|
1668
|
+
}
|
|
1366
1669
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1367
1670
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1368
1671
|
return {
|
|
@@ -1411,17 +1714,12 @@ var init_tsdoc_parser = __esm({
|
|
|
1411
1714
|
minItems: "minItems",
|
|
1412
1715
|
maxItems: "maxItems"
|
|
1413
1716
|
};
|
|
1414
|
-
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1717
|
+
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1415
1718
|
}
|
|
1416
1719
|
});
|
|
1417
1720
|
|
|
1418
1721
|
// src/analyzer/jsdoc-constraints.ts
|
|
1419
1722
|
import * as ts3 from "typescript";
|
|
1420
|
-
import {
|
|
1421
|
-
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
|
|
1422
|
-
isBuiltinConstraintName as isBuiltinConstraintName2,
|
|
1423
|
-
normalizeConstraintTagName as normalizeConstraintTagName2
|
|
1424
|
-
} from "@formspec/core";
|
|
1425
1723
|
function extractJSDocConstraintNodes(node, file = "") {
|
|
1426
1724
|
const result = parseTSDocTags(node, file);
|
|
1427
1725
|
return [...result.constraints];
|
|
@@ -1467,7 +1765,6 @@ var init_jsdoc_constraints = __esm({
|
|
|
1467
1765
|
"src/analyzer/jsdoc-constraints.ts"() {
|
|
1468
1766
|
"use strict";
|
|
1469
1767
|
init_tsdoc_parser();
|
|
1470
|
-
init_json_utils();
|
|
1471
1768
|
}
|
|
1472
1769
|
});
|
|
1473
1770
|
|
|
@@ -1484,6 +1781,7 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1484
1781
|
const fields = [];
|
|
1485
1782
|
const fieldLayouts = [];
|
|
1486
1783
|
const typeRegistry = {};
|
|
1784
|
+
const annotations = extractJSDocAnnotationNodes(classDecl, file);
|
|
1487
1785
|
const visiting = /* @__PURE__ */ new Set();
|
|
1488
1786
|
const instanceMethods = [];
|
|
1489
1787
|
const staticMethods = [];
|
|
@@ -1506,12 +1804,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1506
1804
|
}
|
|
1507
1805
|
}
|
|
1508
1806
|
}
|
|
1509
|
-
return {
|
|
1807
|
+
return {
|
|
1808
|
+
name,
|
|
1809
|
+
fields,
|
|
1810
|
+
fieldLayouts,
|
|
1811
|
+
typeRegistry,
|
|
1812
|
+
...annotations.length > 0 && { annotations },
|
|
1813
|
+
instanceMethods,
|
|
1814
|
+
staticMethods
|
|
1815
|
+
};
|
|
1510
1816
|
}
|
|
1511
1817
|
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1512
1818
|
const name = interfaceDecl.name.text;
|
|
1513
1819
|
const fields = [];
|
|
1514
1820
|
const typeRegistry = {};
|
|
1821
|
+
const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
|
|
1515
1822
|
const visiting = /* @__PURE__ */ new Set();
|
|
1516
1823
|
for (const member of interfaceDecl.members) {
|
|
1517
1824
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -1522,7 +1829,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
1522
1829
|
}
|
|
1523
1830
|
}
|
|
1524
1831
|
const fieldLayouts = fields.map(() => ({}));
|
|
1525
|
-
return {
|
|
1832
|
+
return {
|
|
1833
|
+
name,
|
|
1834
|
+
fields,
|
|
1835
|
+
fieldLayouts,
|
|
1836
|
+
typeRegistry,
|
|
1837
|
+
...annotations.length > 0 && { annotations },
|
|
1838
|
+
instanceMethods: [],
|
|
1839
|
+
staticMethods: []
|
|
1840
|
+
};
|
|
1526
1841
|
}
|
|
1527
1842
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1528
1843
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
@@ -1537,6 +1852,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1537
1852
|
const name = typeAlias.name.text;
|
|
1538
1853
|
const fields = [];
|
|
1539
1854
|
const typeRegistry = {};
|
|
1855
|
+
const annotations = extractJSDocAnnotationNodes(typeAlias, file);
|
|
1540
1856
|
const visiting = /* @__PURE__ */ new Set();
|
|
1541
1857
|
for (const member of typeAlias.type.members) {
|
|
1542
1858
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -1553,6 +1869,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1553
1869
|
fields,
|
|
1554
1870
|
fieldLayouts: fields.map(() => ({})),
|
|
1555
1871
|
typeRegistry,
|
|
1872
|
+
...annotations.length > 0 && { annotations },
|
|
1556
1873
|
instanceMethods: [],
|
|
1557
1874
|
staticMethods: []
|
|
1558
1875
|
}
|
|
@@ -1566,18 +1883,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1566
1883
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1567
1884
|
const optional = prop.questionToken !== void 0;
|
|
1568
1885
|
const provenance = provenanceForNode(prop, file);
|
|
1569
|
-
|
|
1886
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
1570
1887
|
const constraints = [];
|
|
1571
1888
|
if (prop.type) {
|
|
1572
1889
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1573
1890
|
}
|
|
1574
1891
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1575
|
-
|
|
1892
|
+
let annotations = [];
|
|
1576
1893
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1577
1894
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1578
|
-
if (defaultAnnotation) {
|
|
1895
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1579
1896
|
annotations.push(defaultAnnotation);
|
|
1580
1897
|
}
|
|
1898
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1581
1899
|
return {
|
|
1582
1900
|
kind: "field",
|
|
1583
1901
|
name,
|
|
@@ -1596,14 +1914,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1596
1914
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1597
1915
|
const optional = prop.questionToken !== void 0;
|
|
1598
1916
|
const provenance = provenanceForNode(prop, file);
|
|
1599
|
-
|
|
1917
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
1600
1918
|
const constraints = [];
|
|
1601
1919
|
if (prop.type) {
|
|
1602
1920
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1603
1921
|
}
|
|
1604
1922
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1605
|
-
|
|
1923
|
+
let annotations = [];
|
|
1606
1924
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1925
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1607
1926
|
return {
|
|
1608
1927
|
kind: "field",
|
|
1609
1928
|
name,
|
|
@@ -1614,7 +1933,69 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1614
1933
|
provenance
|
|
1615
1934
|
};
|
|
1616
1935
|
}
|
|
1617
|
-
function
|
|
1936
|
+
function applyEnumMemberDisplayNames(type, annotations) {
|
|
1937
|
+
if (!annotations.some(
|
|
1938
|
+
(annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
|
|
1939
|
+
)) {
|
|
1940
|
+
return { type, annotations: [...annotations] };
|
|
1941
|
+
}
|
|
1942
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
1943
|
+
const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
|
|
1944
|
+
if (consumed.size === 0) {
|
|
1945
|
+
return { type, annotations: [...annotations] };
|
|
1946
|
+
}
|
|
1947
|
+
return {
|
|
1948
|
+
type: nextType,
|
|
1949
|
+
annotations: annotations.filter((annotation) => !consumed.has(annotation))
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
function rewriteEnumDisplayNames(type, annotations, consumed) {
|
|
1953
|
+
switch (type.kind) {
|
|
1954
|
+
case "enum":
|
|
1955
|
+
return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
|
|
1956
|
+
case "union": {
|
|
1957
|
+
return {
|
|
1958
|
+
...type,
|
|
1959
|
+
members: type.members.map(
|
|
1960
|
+
(member) => rewriteEnumDisplayNames(member, annotations, consumed)
|
|
1961
|
+
)
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
default:
|
|
1965
|
+
return type;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
|
|
1969
|
+
const displayNames = /* @__PURE__ */ new Map();
|
|
1970
|
+
for (const annotation of annotations) {
|
|
1971
|
+
if (annotation.annotationKind !== "displayName") continue;
|
|
1972
|
+
const parsed = parseEnumMemberDisplayName(annotation.value);
|
|
1973
|
+
if (!parsed) continue;
|
|
1974
|
+
consumed.add(annotation);
|
|
1975
|
+
const member = type.members.find((m) => String(m.value) === parsed.value);
|
|
1976
|
+
if (!member) continue;
|
|
1977
|
+
displayNames.set(String(member.value), parsed.label);
|
|
1978
|
+
}
|
|
1979
|
+
if (displayNames.size === 0) {
|
|
1980
|
+
return type;
|
|
1981
|
+
}
|
|
1982
|
+
return {
|
|
1983
|
+
...type,
|
|
1984
|
+
members: type.members.map((member) => {
|
|
1985
|
+
const displayName = displayNames.get(String(member.value));
|
|
1986
|
+
return displayName !== void 0 ? { ...member, displayName } : member;
|
|
1987
|
+
})
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
function parseEnumMemberDisplayName(value) {
|
|
1991
|
+
const trimmed = value.trim();
|
|
1992
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
|
|
1993
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1994
|
+
const label = match[2].trim();
|
|
1995
|
+
if (label === "") return null;
|
|
1996
|
+
return { value: match[1], label };
|
|
1997
|
+
}
|
|
1998
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1618
1999
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1619
2000
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1620
2001
|
}
|
|
@@ -1643,7 +2024,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1643
2024
|
};
|
|
1644
2025
|
}
|
|
1645
2026
|
if (type.isUnion()) {
|
|
1646
|
-
return resolveUnionType(type, checker, file, typeRegistry, visiting);
|
|
2027
|
+
return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
|
|
1647
2028
|
}
|
|
1648
2029
|
if (checker.isArrayType(type)) {
|
|
1649
2030
|
return resolveArrayType(type, checker, file, typeRegistry, visiting);
|
|
@@ -1653,70 +2034,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1653
2034
|
}
|
|
1654
2035
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1655
2036
|
}
|
|
1656
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
2037
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
2038
|
+
const typeName = getNamedTypeName(type);
|
|
2039
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2040
|
+
if (typeName && typeName in typeRegistry) {
|
|
2041
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2042
|
+
}
|
|
1657
2043
|
const allTypes = type.types;
|
|
1658
2044
|
const nonNullTypes = allTypes.filter(
|
|
1659
2045
|
(t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1660
2046
|
);
|
|
1661
2047
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
2048
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2049
|
+
if (namedDecl) {
|
|
2050
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
2051
|
+
memberDisplayNames.set(value, label);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
if (sourceNode) {
|
|
2055
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
2056
|
+
memberDisplayNames.set(value, label);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
const registerNamed = (result) => {
|
|
2060
|
+
if (!typeName) {
|
|
2061
|
+
return result;
|
|
2062
|
+
}
|
|
2063
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2064
|
+
typeRegistry[typeName] = {
|
|
2065
|
+
name: typeName,
|
|
2066
|
+
type: result,
|
|
2067
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2068
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
2069
|
+
};
|
|
2070
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2071
|
+
};
|
|
2072
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
2073
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
2074
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2075
|
+
});
|
|
1662
2076
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1663
2077
|
if (isBooleanUnion2) {
|
|
1664
2078
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
}
|
|
1671
|
-
return boolNode;
|
|
2079
|
+
const result = hasNull ? {
|
|
2080
|
+
kind: "union",
|
|
2081
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2082
|
+
} : boolNode;
|
|
2083
|
+
return registerNamed(result);
|
|
1672
2084
|
}
|
|
1673
2085
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1674
2086
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1675
2087
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1676
2088
|
const enumNode = {
|
|
1677
2089
|
kind: "enum",
|
|
1678
|
-
members: stringTypes.map((t) =>
|
|
2090
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1679
2091
|
};
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
}
|
|
1686
|
-
return enumNode;
|
|
2092
|
+
const result = hasNull ? {
|
|
2093
|
+
kind: "union",
|
|
2094
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2095
|
+
} : enumNode;
|
|
2096
|
+
return registerNamed(result);
|
|
1687
2097
|
}
|
|
1688
2098
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1689
2099
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1690
2100
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1691
2101
|
const enumNode = {
|
|
1692
2102
|
kind: "enum",
|
|
1693
|
-
members: numberTypes.map((t) =>
|
|
2103
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1694
2104
|
};
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
}
|
|
1701
|
-
return enumNode;
|
|
2105
|
+
const result = hasNull ? {
|
|
2106
|
+
kind: "union",
|
|
2107
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2108
|
+
} : enumNode;
|
|
2109
|
+
return registerNamed(result);
|
|
1702
2110
|
}
|
|
1703
2111
|
if (nonNullTypes.length === 1 && nonNullTypes[0]) {
|
|
1704
|
-
const inner = resolveTypeNode(
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2112
|
+
const inner = resolveTypeNode(
|
|
2113
|
+
nonNullTypes[0],
|
|
2114
|
+
checker,
|
|
2115
|
+
file,
|
|
2116
|
+
typeRegistry,
|
|
2117
|
+
visiting,
|
|
2118
|
+
sourceNode
|
|
2119
|
+
);
|
|
2120
|
+
const result = hasNull ? {
|
|
2121
|
+
kind: "union",
|
|
2122
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
2123
|
+
} : inner;
|
|
2124
|
+
return registerNamed(result);
|
|
1712
2125
|
}
|
|
1713
2126
|
const members = nonNullTypes.map(
|
|
1714
|
-
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
|
|
2127
|
+
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
|
|
1715
2128
|
);
|
|
1716
2129
|
if (hasNull) {
|
|
1717
2130
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1718
2131
|
}
|
|
1719
|
-
return { kind: "union", members };
|
|
2132
|
+
return registerNamed({ kind: "union", members });
|
|
1720
2133
|
}
|
|
1721
2134
|
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1722
2135
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
@@ -1724,15 +2137,92 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
|
1724
2137
|
const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
|
|
1725
2138
|
return { kind: "array", items };
|
|
1726
2139
|
}
|
|
2140
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
2141
|
+
if (type.getProperties().length > 0) {
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
|
|
2145
|
+
if (!indexInfo) {
|
|
2146
|
+
return null;
|
|
2147
|
+
}
|
|
2148
|
+
const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
|
|
2149
|
+
return { kind: "record", valueType };
|
|
2150
|
+
}
|
|
2151
|
+
function typeNodeContainsReference(type, targetName) {
|
|
2152
|
+
switch (type.kind) {
|
|
2153
|
+
case "reference":
|
|
2154
|
+
return type.name === targetName;
|
|
2155
|
+
case "array":
|
|
2156
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
2157
|
+
case "record":
|
|
2158
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
2159
|
+
case "union":
|
|
2160
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
2161
|
+
case "object":
|
|
2162
|
+
return type.properties.some(
|
|
2163
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
2164
|
+
);
|
|
2165
|
+
case "primitive":
|
|
2166
|
+
case "enum":
|
|
2167
|
+
case "dynamic":
|
|
2168
|
+
case "custom":
|
|
2169
|
+
return false;
|
|
2170
|
+
default: {
|
|
2171
|
+
const _exhaustive = type;
|
|
2172
|
+
return _exhaustive;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
1727
2176
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
2177
|
+
const typeName = getNamedTypeName(type);
|
|
2178
|
+
const namedTypeName = typeName ?? void 0;
|
|
2179
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2180
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2181
|
+
const clearNamedTypeRegistration = () => {
|
|
2182
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
2186
|
+
};
|
|
1728
2187
|
if (visiting.has(type)) {
|
|
2188
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2189
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2190
|
+
}
|
|
1729
2191
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1730
2192
|
}
|
|
2193
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
2194
|
+
typeRegistry[namedTypeName] = {
|
|
2195
|
+
name: namedTypeName,
|
|
2196
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2197
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
1731
2200
|
visiting.add(type);
|
|
1732
|
-
|
|
1733
|
-
|
|
2201
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
2202
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2203
|
+
visiting.delete(type);
|
|
2204
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
2208
|
+
if (recordNode) {
|
|
1734
2209
|
visiting.delete(type);
|
|
1735
|
-
|
|
2210
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2211
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
2212
|
+
if (!isRecursiveRecord) {
|
|
2213
|
+
clearNamedTypeRegistration();
|
|
2214
|
+
return recordNode;
|
|
2215
|
+
}
|
|
2216
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2217
|
+
typeRegistry[namedTypeName] = {
|
|
2218
|
+
name: namedTypeName,
|
|
2219
|
+
type: recordNode,
|
|
2220
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2221
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2222
|
+
};
|
|
2223
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2224
|
+
}
|
|
2225
|
+
return recordNode;
|
|
1736
2226
|
}
|
|
1737
2227
|
const properties = [];
|
|
1738
2228
|
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
|
|
@@ -1741,7 +2231,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1741
2231
|
if (!declaration) continue;
|
|
1742
2232
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1743
2233
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1744
|
-
const propTypeNode = resolveTypeNode(
|
|
2234
|
+
const propTypeNode = resolveTypeNode(
|
|
2235
|
+
propType,
|
|
2236
|
+
checker,
|
|
2237
|
+
file,
|
|
2238
|
+
typeRegistry,
|
|
2239
|
+
visiting,
|
|
2240
|
+
declaration
|
|
2241
|
+
);
|
|
1745
2242
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1746
2243
|
properties.push({
|
|
1747
2244
|
name: prop.name,
|
|
@@ -1756,15 +2253,17 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1756
2253
|
const objectNode = {
|
|
1757
2254
|
kind: "object",
|
|
1758
2255
|
properties,
|
|
1759
|
-
additionalProperties:
|
|
2256
|
+
additionalProperties: true
|
|
1760
2257
|
};
|
|
1761
|
-
if (
|
|
1762
|
-
|
|
1763
|
-
|
|
2258
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2259
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2260
|
+
typeRegistry[namedTypeName] = {
|
|
2261
|
+
name: namedTypeName,
|
|
1764
2262
|
type: objectNode,
|
|
1765
|
-
|
|
2263
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2264
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1766
2265
|
};
|
|
1767
|
-
return { kind: "reference", name:
|
|
2266
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1768
2267
|
}
|
|
1769
2268
|
return objectNode;
|
|
1770
2269
|
}
|
|
@@ -1855,6 +2354,12 @@ function provenanceForNode(node, file) {
|
|
|
1855
2354
|
function provenanceForFile(file) {
|
|
1856
2355
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1857
2356
|
}
|
|
2357
|
+
function provenanceForDeclaration(node, file) {
|
|
2358
|
+
if (!node) {
|
|
2359
|
+
return provenanceForFile(file);
|
|
2360
|
+
}
|
|
2361
|
+
return provenanceForNode(node, file);
|
|
2362
|
+
}
|
|
1858
2363
|
function getNamedTypeName(type) {
|
|
1859
2364
|
const symbol = type.getSymbol();
|
|
1860
2365
|
if (symbol?.declarations) {
|
|
@@ -1873,6 +2378,20 @@ function getNamedTypeName(type) {
|
|
|
1873
2378
|
}
|
|
1874
2379
|
return null;
|
|
1875
2380
|
}
|
|
2381
|
+
function getNamedTypeDeclaration(type) {
|
|
2382
|
+
const symbol = type.getSymbol();
|
|
2383
|
+
if (symbol?.declarations) {
|
|
2384
|
+
const decl = symbol.declarations[0];
|
|
2385
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
2386
|
+
return decl;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
const aliasSymbol = type.aliasSymbol;
|
|
2390
|
+
if (aliasSymbol?.declarations) {
|
|
2391
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2392
|
+
}
|
|
2393
|
+
return void 0;
|
|
2394
|
+
}
|
|
1876
2395
|
function analyzeMethod(method, checker) {
|
|
1877
2396
|
if (!ts4.isIdentifier(method.name)) {
|
|
1878
2397
|
return null;
|
|
@@ -1913,11 +2432,17 @@ function detectFormSpecReference(typeNode) {
|
|
|
1913
2432
|
}
|
|
1914
2433
|
return null;
|
|
1915
2434
|
}
|
|
1916
|
-
var MAX_ALIAS_CHAIN_DEPTH;
|
|
2435
|
+
var RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
|
|
1917
2436
|
var init_class_analyzer = __esm({
|
|
1918
2437
|
"src/analyzer/class-analyzer.ts"() {
|
|
1919
2438
|
"use strict";
|
|
1920
2439
|
init_jsdoc_constraints();
|
|
2440
|
+
init_tsdoc_parser();
|
|
2441
|
+
RESOLVING_TYPE_PLACEHOLDER = {
|
|
2442
|
+
kind: "object",
|
|
2443
|
+
properties: [],
|
|
2444
|
+
additionalProperties: true
|
|
2445
|
+
};
|
|
1921
2446
|
MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1922
2447
|
}
|
|
1923
2448
|
});
|
|
@@ -1975,14 +2500,226 @@ var init_class_schema = __esm({
|
|
|
1975
2500
|
}
|
|
1976
2501
|
});
|
|
1977
2502
|
|
|
2503
|
+
// src/generators/mixed-authoring.ts
|
|
2504
|
+
function buildMixedAuthoringSchemas(options) {
|
|
2505
|
+
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2506
|
+
const analysis = analyzeNamedType(filePath, typeName);
|
|
2507
|
+
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2508
|
+
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2509
|
+
return {
|
|
2510
|
+
jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
|
|
2511
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
function analyzeNamedType(filePath, typeName) {
|
|
2515
|
+
const ctx = createProgramContext(filePath);
|
|
2516
|
+
const source = { file: filePath };
|
|
2517
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2518
|
+
if (classDecl !== null) {
|
|
2519
|
+
return analyzeClassToIR(classDecl, ctx.checker, source.file);
|
|
2520
|
+
}
|
|
2521
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2522
|
+
if (interfaceDecl !== null) {
|
|
2523
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
|
|
2524
|
+
}
|
|
2525
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2526
|
+
if (typeAlias !== null) {
|
|
2527
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
|
|
2528
|
+
if (result.ok) {
|
|
2529
|
+
return result.analysis;
|
|
2530
|
+
}
|
|
2531
|
+
throw new Error(result.error);
|
|
2532
|
+
}
|
|
2533
|
+
throw new Error(
|
|
2534
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2538
|
+
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2539
|
+
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
2540
|
+
if (overlayFields.length === 0) {
|
|
2541
|
+
return analysis;
|
|
2542
|
+
}
|
|
2543
|
+
const overlayByName = /* @__PURE__ */ new Map();
|
|
2544
|
+
for (const field of overlayFields) {
|
|
2545
|
+
if (overlayByName.has(field.name)) {
|
|
2546
|
+
throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
|
|
2547
|
+
}
|
|
2548
|
+
overlayByName.set(field.name, field);
|
|
2549
|
+
}
|
|
2550
|
+
const mergedFields = [];
|
|
2551
|
+
for (const baseField of analysis.fields) {
|
|
2552
|
+
const overlayField = overlayByName.get(baseField.name);
|
|
2553
|
+
if (overlayField === void 0) {
|
|
2554
|
+
mergedFields.push(baseField);
|
|
2555
|
+
continue;
|
|
2556
|
+
}
|
|
2557
|
+
mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
|
|
2558
|
+
overlayByName.delete(baseField.name);
|
|
2559
|
+
}
|
|
2560
|
+
if (overlayByName.size > 0) {
|
|
2561
|
+
const unknownFields = [...overlayByName.keys()].sort().join(", ");
|
|
2562
|
+
throw new Error(
|
|
2563
|
+
`Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
|
|
2564
|
+
);
|
|
2565
|
+
}
|
|
2566
|
+
return {
|
|
2567
|
+
...analysis,
|
|
2568
|
+
fields: mergedFields
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
function collectOverlayFields(elements) {
|
|
2572
|
+
const fields = [];
|
|
2573
|
+
for (const element of elements) {
|
|
2574
|
+
switch (element.kind) {
|
|
2575
|
+
case "field":
|
|
2576
|
+
fields.push(element);
|
|
2577
|
+
break;
|
|
2578
|
+
case "group":
|
|
2579
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2580
|
+
break;
|
|
2581
|
+
case "conditional":
|
|
2582
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2583
|
+
break;
|
|
2584
|
+
default: {
|
|
2585
|
+
const _exhaustive = element;
|
|
2586
|
+
void _exhaustive;
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
return fields;
|
|
2591
|
+
}
|
|
2592
|
+
function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
|
|
2593
|
+
assertSupportedOverlayField(baseField, overlayField);
|
|
2594
|
+
return {
|
|
2595
|
+
...baseField,
|
|
2596
|
+
type: mergeFieldType(baseField, overlayField, typeRegistry),
|
|
2597
|
+
annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
function assertSupportedOverlayField(baseField, overlayField) {
|
|
2601
|
+
if (overlayField.constraints.length > 0) {
|
|
2602
|
+
throw new Error(
|
|
2603
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
2604
|
+
);
|
|
2605
|
+
}
|
|
2606
|
+
if (overlayField.required) {
|
|
2607
|
+
throw new Error(
|
|
2608
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
2609
|
+
);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
function mergeFieldType(baseField, overlayField, typeRegistry) {
|
|
2613
|
+
const { type: baseType } = baseField;
|
|
2614
|
+
const { type: overlayType } = overlayField;
|
|
2615
|
+
if (overlayType.kind === "object" || overlayType.kind === "array") {
|
|
2616
|
+
throw new Error(
|
|
2617
|
+
`Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
if (overlayType.kind === "dynamic") {
|
|
2621
|
+
if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
|
|
2622
|
+
throw new Error(
|
|
2623
|
+
`Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
return overlayType;
|
|
2627
|
+
}
|
|
2628
|
+
if (!isSameStaticTypeShape(baseType, overlayType)) {
|
|
2629
|
+
throw new Error(
|
|
2630
|
+
`Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
|
|
2631
|
+
);
|
|
2632
|
+
}
|
|
2633
|
+
return baseType;
|
|
2634
|
+
}
|
|
2635
|
+
function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
|
|
2636
|
+
const overlayType = overlayField.type;
|
|
2637
|
+
if (overlayType.kind !== "dynamic") {
|
|
2638
|
+
return false;
|
|
2639
|
+
}
|
|
2640
|
+
const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
|
|
2641
|
+
if (resolvedBaseType === null) {
|
|
2642
|
+
return false;
|
|
2643
|
+
}
|
|
2644
|
+
if (overlayType.dynamicKind === "enum") {
|
|
2645
|
+
return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
|
|
2646
|
+
}
|
|
2647
|
+
return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
|
|
2648
|
+
}
|
|
2649
|
+
function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
|
|
2650
|
+
if (type.kind !== "reference") {
|
|
2651
|
+
return type;
|
|
2652
|
+
}
|
|
2653
|
+
if (seen.has(type.name)) {
|
|
2654
|
+
return null;
|
|
2655
|
+
}
|
|
2656
|
+
const definition = typeRegistry[type.name];
|
|
2657
|
+
if (definition === void 0) {
|
|
2658
|
+
return null;
|
|
2659
|
+
}
|
|
2660
|
+
seen.add(type.name);
|
|
2661
|
+
return resolveReferenceType(definition.type, typeRegistry, seen);
|
|
2662
|
+
}
|
|
2663
|
+
function isSameStaticTypeShape(baseType, overlayType) {
|
|
2664
|
+
if (baseType.kind !== overlayType.kind) {
|
|
2665
|
+
return false;
|
|
2666
|
+
}
|
|
2667
|
+
switch (baseType.kind) {
|
|
2668
|
+
case "primitive":
|
|
2669
|
+
return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
|
|
2670
|
+
case "enum":
|
|
2671
|
+
return overlayType.kind === "enum";
|
|
2672
|
+
case "dynamic":
|
|
2673
|
+
return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
|
|
2674
|
+
case "record":
|
|
2675
|
+
return overlayType.kind === "record";
|
|
2676
|
+
case "reference":
|
|
2677
|
+
return overlayType.kind === "reference" && baseType.name === overlayType.name;
|
|
2678
|
+
case "union":
|
|
2679
|
+
return overlayType.kind === "union";
|
|
2680
|
+
case "custom":
|
|
2681
|
+
return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
|
|
2682
|
+
case "object":
|
|
2683
|
+
case "array":
|
|
2684
|
+
return true;
|
|
2685
|
+
default: {
|
|
2686
|
+
const _exhaustive = baseType;
|
|
2687
|
+
return _exhaustive;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
2692
|
+
const baseKeys = new Set(baseAnnotations.map(annotationKey));
|
|
2693
|
+
const overlayOnly = overlayAnnotations.filter(
|
|
2694
|
+
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
2695
|
+
);
|
|
2696
|
+
return [...overlayOnly, ...baseAnnotations];
|
|
2697
|
+
}
|
|
2698
|
+
function annotationKey(annotation) {
|
|
2699
|
+
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
2700
|
+
}
|
|
2701
|
+
var init_mixed_authoring = __esm({
|
|
2702
|
+
"src/generators/mixed-authoring.ts"() {
|
|
2703
|
+
"use strict";
|
|
2704
|
+
init_ir_generator();
|
|
2705
|
+
init_ir_generator2();
|
|
2706
|
+
init_canonicalize();
|
|
2707
|
+
init_program();
|
|
2708
|
+
init_class_analyzer();
|
|
2709
|
+
}
|
|
2710
|
+
});
|
|
2711
|
+
|
|
1978
2712
|
// src/index.ts
|
|
1979
2713
|
var index_exports = {};
|
|
1980
2714
|
__export(index_exports, {
|
|
1981
2715
|
buildFormSchemas: () => buildFormSchemas,
|
|
2716
|
+
buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
|
|
1982
2717
|
categorizationSchema: () => categorizationSchema,
|
|
1983
2718
|
categorySchema: () => categorySchema,
|
|
1984
2719
|
controlSchema: () => controlSchema,
|
|
2720
|
+
createExtensionRegistry: () => createExtensionRegistry,
|
|
1985
2721
|
generateJsonSchema: () => generateJsonSchema,
|
|
2722
|
+
generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
|
|
1986
2723
|
generateSchemas: () => generateSchemas,
|
|
1987
2724
|
generateSchemasFromClass: () => generateSchemasFromClass,
|
|
1988
2725
|
generateUiSchema: () => generateUiSchema,
|
|
@@ -2005,15 +2742,19 @@ __export(index_exports, {
|
|
|
2005
2742
|
});
|
|
2006
2743
|
import * as fs from "fs";
|
|
2007
2744
|
import * as path2 from "path";
|
|
2008
|
-
function buildFormSchemas(form) {
|
|
2745
|
+
function buildFormSchemas(form, options) {
|
|
2009
2746
|
return {
|
|
2010
|
-
jsonSchema: generateJsonSchema(form),
|
|
2747
|
+
jsonSchema: generateJsonSchema(form, options),
|
|
2011
2748
|
uiSchema: generateUiSchema(form)
|
|
2012
2749
|
};
|
|
2013
2750
|
}
|
|
2014
2751
|
function writeSchemas(form, options) {
|
|
2015
|
-
const { outDir, name = "schema", indent = 2 } = options;
|
|
2016
|
-
const
|
|
2752
|
+
const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
|
|
2753
|
+
const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
|
|
2754
|
+
extensionRegistry,
|
|
2755
|
+
vendorPrefix
|
|
2756
|
+
};
|
|
2757
|
+
const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
|
|
2017
2758
|
if (!fs.existsSync(outDir)) {
|
|
2018
2759
|
fs.mkdirSync(outDir, { recursive: true });
|
|
2019
2760
|
}
|
|
@@ -2028,12 +2769,16 @@ var init_index = __esm({
|
|
|
2028
2769
|
"use strict";
|
|
2029
2770
|
init_generator();
|
|
2030
2771
|
init_generator2();
|
|
2772
|
+
init_ir_generator();
|
|
2031
2773
|
init_types();
|
|
2774
|
+
init_extensions();
|
|
2032
2775
|
init_schema();
|
|
2033
2776
|
init_schema2();
|
|
2034
2777
|
init_generator();
|
|
2778
|
+
init_ir_generator();
|
|
2035
2779
|
init_generator2();
|
|
2036
2780
|
init_class_schema();
|
|
2781
|
+
init_mixed_authoring();
|
|
2037
2782
|
}
|
|
2038
2783
|
});
|
|
2039
2784
|
|