@formspec/build 0.1.0-alpha.14 → 0.1.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/extension-runtime.integration.test.d.ts +2 -0
- package/dist/__tests__/extension-runtime.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts +22 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +6 -6
- package/dist/__tests__/fixtures/example-interface-types.d.ts +26 -26
- package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -1
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +30 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
- package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
- package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts +17 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +11 -4
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +5 -3
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +7 -51
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +25 -9
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +546 -102
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +15 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +544 -102
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +170 -6
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +877 -128
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +876 -131
- package/dist/cli.js.map +1 -1
- package/dist/generators/mixed-authoring.d.ts +45 -0
- package/dist/generators/mixed-authoring.d.ts.map +1 -0
- package/dist/index.cjs +850 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +847 -129
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +946 -187
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +944 -189
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/generator.d.ts +8 -2
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +27 -4
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/json-schema/types.d.ts +1 -1
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts +3 -7
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -10
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -31,10 +31,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
buildFormSchemas: () => buildFormSchemas,
|
|
34
|
+
buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
|
|
34
35
|
categorizationSchema: () => categorizationSchema,
|
|
35
36
|
categorySchema: () => categorySchema,
|
|
36
37
|
controlSchema: () => controlSchema,
|
|
38
|
+
createExtensionRegistry: () => createExtensionRegistry,
|
|
37
39
|
generateJsonSchema: () => generateJsonSchema,
|
|
40
|
+
generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
|
|
38
41
|
generateSchemas: () => generateSchemas,
|
|
39
42
|
generateSchemasFromClass: () => generateSchemasFromClass,
|
|
40
43
|
generateUiSchema: () => generateUiSchema,
|
|
@@ -236,7 +239,7 @@ function canonicalizeArrayField(field) {
|
|
|
236
239
|
const itemsType = {
|
|
237
240
|
kind: "object",
|
|
238
241
|
properties: itemProperties,
|
|
239
|
-
additionalProperties:
|
|
242
|
+
additionalProperties: true
|
|
240
243
|
};
|
|
241
244
|
const type = { kind: "array", items: itemsType };
|
|
242
245
|
const constraints = [];
|
|
@@ -271,7 +274,7 @@ function canonicalizeObjectField(field) {
|
|
|
271
274
|
const type = {
|
|
272
275
|
kind: "object",
|
|
273
276
|
properties,
|
|
274
|
-
additionalProperties:
|
|
277
|
+
additionalProperties: true
|
|
275
278
|
};
|
|
276
279
|
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
277
280
|
}
|
|
@@ -384,6 +387,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
384
387
|
irVersion: import_core2.IR_VERSION,
|
|
385
388
|
elements,
|
|
386
389
|
typeRegistry: analysis.typeRegistry,
|
|
390
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
387
391
|
provenance
|
|
388
392
|
};
|
|
389
393
|
}
|
|
@@ -443,13 +447,26 @@ function wrapInConditional(field, layout, provenance) {
|
|
|
443
447
|
}
|
|
444
448
|
|
|
445
449
|
// src/json-schema/ir-generator.ts
|
|
446
|
-
function makeContext() {
|
|
447
|
-
|
|
450
|
+
function makeContext(options) {
|
|
451
|
+
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
452
|
+
if (!vendorPrefix.startsWith("x-")) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
defs: {},
|
|
459
|
+
extensionRegistry: options?.extensionRegistry,
|
|
460
|
+
vendorPrefix
|
|
461
|
+
};
|
|
448
462
|
}
|
|
449
|
-
function generateJsonSchemaFromIR(ir) {
|
|
450
|
-
const ctx = makeContext();
|
|
463
|
+
function generateJsonSchemaFromIR(ir, options) {
|
|
464
|
+
const ctx = makeContext(options);
|
|
451
465
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
452
466
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
467
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
468
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
469
|
+
}
|
|
453
470
|
}
|
|
454
471
|
const properties = {};
|
|
455
472
|
const required = [];
|
|
@@ -461,6 +478,9 @@ function generateJsonSchemaFromIR(ir) {
|
|
|
461
478
|
properties,
|
|
462
479
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
463
480
|
};
|
|
481
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
482
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
483
|
+
}
|
|
464
484
|
if (Object.keys(ctx.defs).length > 0) {
|
|
465
485
|
result.$defs = ctx.defs;
|
|
466
486
|
}
|
|
@@ -490,25 +510,54 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
490
510
|
}
|
|
491
511
|
function generateFieldSchema(field, ctx) {
|
|
492
512
|
const schema = generateTypeNode(field.type, ctx);
|
|
513
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
493
514
|
const directConstraints = [];
|
|
515
|
+
const itemConstraints = [];
|
|
494
516
|
const pathConstraints = [];
|
|
495
517
|
for (const c of field.constraints) {
|
|
496
518
|
if (c.path) {
|
|
497
519
|
pathConstraints.push(c);
|
|
520
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
521
|
+
itemConstraints.push(c);
|
|
498
522
|
} else {
|
|
499
523
|
directConstraints.push(c);
|
|
500
524
|
}
|
|
501
525
|
}
|
|
502
|
-
applyConstraints(schema, directConstraints);
|
|
503
|
-
|
|
526
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
527
|
+
if (itemStringSchema !== void 0) {
|
|
528
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
529
|
+
}
|
|
530
|
+
const rootAnnotations = [];
|
|
531
|
+
const itemAnnotations = [];
|
|
532
|
+
for (const annotation of field.annotations) {
|
|
533
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
534
|
+
itemAnnotations.push(annotation);
|
|
535
|
+
} else {
|
|
536
|
+
rootAnnotations.push(annotation);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
540
|
+
if (itemStringSchema !== void 0) {
|
|
541
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
542
|
+
}
|
|
504
543
|
if (pathConstraints.length === 0) {
|
|
505
544
|
return schema;
|
|
506
545
|
}
|
|
507
|
-
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
546
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
508
547
|
}
|
|
509
|
-
function
|
|
548
|
+
function isStringItemConstraint(constraint) {
|
|
549
|
+
switch (constraint.constraintKind) {
|
|
550
|
+
case "minLength":
|
|
551
|
+
case "maxLength":
|
|
552
|
+
case "pattern":
|
|
553
|
+
return true;
|
|
554
|
+
default:
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
510
559
|
if (schema.type === "array" && schema.items) {
|
|
511
|
-
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
560
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
512
561
|
return schema;
|
|
513
562
|
}
|
|
514
563
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -522,7 +571,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
|
522
571
|
const propertyOverrides = {};
|
|
523
572
|
for (const [target, constraints] of byTarget) {
|
|
524
573
|
const subSchema = {};
|
|
525
|
-
applyConstraints(subSchema, constraints);
|
|
574
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
526
575
|
propertyOverrides[target] = subSchema;
|
|
527
576
|
}
|
|
528
577
|
if (schema.$ref) {
|
|
@@ -566,6 +615,8 @@ function generateTypeNode(type, ctx) {
|
|
|
566
615
|
return generateArrayType(type, ctx);
|
|
567
616
|
case "object":
|
|
568
617
|
return generateObjectType(type, ctx);
|
|
618
|
+
case "record":
|
|
619
|
+
return generateRecordType(type, ctx);
|
|
569
620
|
case "union":
|
|
570
621
|
return generateUnionType(type, ctx);
|
|
571
622
|
case "reference":
|
|
@@ -573,7 +624,7 @@ function generateTypeNode(type, ctx) {
|
|
|
573
624
|
case "dynamic":
|
|
574
625
|
return generateDynamicType(type);
|
|
575
626
|
case "custom":
|
|
576
|
-
return generateCustomType(type);
|
|
627
|
+
return generateCustomType(type, ctx);
|
|
577
628
|
default: {
|
|
578
629
|
const _exhaustive = type;
|
|
579
630
|
return _exhaustive;
|
|
@@ -622,16 +673,27 @@ function generateObjectType(type, ctx) {
|
|
|
622
673
|
}
|
|
623
674
|
return schema;
|
|
624
675
|
}
|
|
676
|
+
function generateRecordType(type, ctx) {
|
|
677
|
+
return {
|
|
678
|
+
type: "object",
|
|
679
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
680
|
+
};
|
|
681
|
+
}
|
|
625
682
|
function generatePropertySchema(prop, ctx) {
|
|
626
683
|
const schema = generateTypeNode(prop.type, ctx);
|
|
627
|
-
applyConstraints(schema, prop.constraints);
|
|
628
|
-
applyAnnotations(schema, prop.annotations);
|
|
684
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
685
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
629
686
|
return schema;
|
|
630
687
|
}
|
|
631
688
|
function generateUnionType(type, ctx) {
|
|
632
689
|
if (isBooleanUnion(type)) {
|
|
633
690
|
return { type: "boolean" };
|
|
634
691
|
}
|
|
692
|
+
if (isNullableUnion(type)) {
|
|
693
|
+
return {
|
|
694
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
695
|
+
};
|
|
696
|
+
}
|
|
635
697
|
return {
|
|
636
698
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
637
699
|
};
|
|
@@ -641,6 +703,13 @@ function isBooleanUnion(type) {
|
|
|
641
703
|
const kinds = type.members.map((m) => m.kind);
|
|
642
704
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
643
705
|
}
|
|
706
|
+
function isNullableUnion(type) {
|
|
707
|
+
if (type.members.length !== 2) return false;
|
|
708
|
+
const nullCount = type.members.filter(
|
|
709
|
+
(m) => m.kind === "primitive" && m.primitiveKind === "null"
|
|
710
|
+
).length;
|
|
711
|
+
return nullCount === 1;
|
|
712
|
+
}
|
|
644
713
|
function generateReferenceType(type) {
|
|
645
714
|
return { $ref: `#/$defs/${type.name}` };
|
|
646
715
|
}
|
|
@@ -661,10 +730,7 @@ function generateDynamicType(type) {
|
|
|
661
730
|
"x-formspec-schemaSource": type.sourceKey
|
|
662
731
|
};
|
|
663
732
|
}
|
|
664
|
-
function
|
|
665
|
-
return { type: "object" };
|
|
666
|
-
}
|
|
667
|
-
function applyConstraints(schema, constraints) {
|
|
733
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
668
734
|
for (const constraint of constraints) {
|
|
669
735
|
switch (constraint.constraintKind) {
|
|
670
736
|
case "minimum":
|
|
@@ -706,9 +772,13 @@ function applyConstraints(schema, constraints) {
|
|
|
706
772
|
case "uniqueItems":
|
|
707
773
|
schema.uniqueItems = constraint.value;
|
|
708
774
|
break;
|
|
775
|
+
case "const":
|
|
776
|
+
schema.const = constraint.value;
|
|
777
|
+
break;
|
|
709
778
|
case "allowedMembers":
|
|
710
779
|
break;
|
|
711
780
|
case "custom":
|
|
781
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
712
782
|
break;
|
|
713
783
|
default: {
|
|
714
784
|
const _exhaustive = constraint;
|
|
@@ -717,7 +787,7 @@ function applyConstraints(schema, constraints) {
|
|
|
717
787
|
}
|
|
718
788
|
}
|
|
719
789
|
}
|
|
720
|
-
function applyAnnotations(schema, annotations) {
|
|
790
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
721
791
|
for (const annotation of annotations) {
|
|
722
792
|
switch (annotation.annotationKind) {
|
|
723
793
|
case "displayName":
|
|
@@ -729,14 +799,21 @@ function applyAnnotations(schema, annotations) {
|
|
|
729
799
|
case "defaultValue":
|
|
730
800
|
schema.default = annotation.value;
|
|
731
801
|
break;
|
|
802
|
+
case "format":
|
|
803
|
+
schema.format = annotation.value;
|
|
804
|
+
break;
|
|
732
805
|
case "deprecated":
|
|
733
806
|
schema.deprecated = true;
|
|
807
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
808
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
809
|
+
}
|
|
734
810
|
break;
|
|
735
811
|
case "placeholder":
|
|
736
812
|
break;
|
|
737
813
|
case "formatHint":
|
|
738
814
|
break;
|
|
739
815
|
case "custom":
|
|
816
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
740
817
|
break;
|
|
741
818
|
default: {
|
|
742
819
|
const _exhaustive = annotation;
|
|
@@ -745,11 +822,41 @@ function applyAnnotations(schema, annotations) {
|
|
|
745
822
|
}
|
|
746
823
|
}
|
|
747
824
|
}
|
|
825
|
+
function generateCustomType(type, ctx) {
|
|
826
|
+
const registration = ctx.extensionRegistry?.findType(type.typeId);
|
|
827
|
+
if (registration === void 0) {
|
|
828
|
+
throw new Error(
|
|
829
|
+
`Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
|
|
833
|
+
}
|
|
834
|
+
function applyCustomConstraint(schema, constraint, ctx) {
|
|
835
|
+
const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
|
|
836
|
+
if (registration === void 0) {
|
|
837
|
+
throw new Error(
|
|
838
|
+
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
|
|
842
|
+
}
|
|
843
|
+
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
844
|
+
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
845
|
+
if (registration === void 0) {
|
|
846
|
+
throw new Error(
|
|
847
|
+
`Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
if (registration.toJsonSchema === void 0) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
|
|
854
|
+
}
|
|
748
855
|
|
|
749
856
|
// src/json-schema/generator.ts
|
|
750
|
-
function generateJsonSchema(form) {
|
|
857
|
+
function generateJsonSchema(form, options) {
|
|
751
858
|
const ir = canonicalizeChainDSL(form);
|
|
752
|
-
return generateJsonSchemaFromIR(ir);
|
|
859
|
+
return generateJsonSchemaFromIR(ir, options);
|
|
753
860
|
}
|
|
754
861
|
|
|
755
862
|
// src/ui-schema/schema.ts
|
|
@@ -887,25 +994,31 @@ function createShowRule(fieldName, value) {
|
|
|
887
994
|
}
|
|
888
995
|
};
|
|
889
996
|
}
|
|
997
|
+
function flattenConditionSchema(scope, schema) {
|
|
998
|
+
if (schema.allOf === void 0) {
|
|
999
|
+
if (scope === "#") {
|
|
1000
|
+
return [schema];
|
|
1001
|
+
}
|
|
1002
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
1003
|
+
return [
|
|
1004
|
+
{
|
|
1005
|
+
properties: {
|
|
1006
|
+
[fieldName]: schema
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
];
|
|
1010
|
+
}
|
|
1011
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
1012
|
+
}
|
|
890
1013
|
function combineRules(parentRule, childRule) {
|
|
891
|
-
const parentCondition = parentRule.condition;
|
|
892
|
-
const childCondition = childRule.condition;
|
|
893
1014
|
return {
|
|
894
1015
|
effect: "SHOW",
|
|
895
1016
|
condition: {
|
|
896
1017
|
scope: "#",
|
|
897
1018
|
schema: {
|
|
898
1019
|
allOf: [
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
902
|
-
}
|
|
903
|
-
},
|
|
904
|
-
{
|
|
905
|
-
properties: {
|
|
906
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
907
|
-
}
|
|
908
|
-
}
|
|
1020
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
1021
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
909
1022
|
]
|
|
910
1023
|
}
|
|
911
1024
|
}
|
|
@@ -913,10 +1026,14 @@ function combineRules(parentRule, childRule) {
|
|
|
913
1026
|
}
|
|
914
1027
|
function fieldNodeToControl(field, parentRule) {
|
|
915
1028
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
1029
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
916
1030
|
const control = {
|
|
917
1031
|
type: "Control",
|
|
918
1032
|
scope: fieldToScope(field.name),
|
|
919
1033
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
1034
|
+
...placeholderAnnotation !== void 0 && {
|
|
1035
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
1036
|
+
},
|
|
920
1037
|
...parentRule !== void 0 && { rule: parentRule }
|
|
921
1038
|
};
|
|
922
1039
|
return control;
|
|
@@ -983,6 +1100,48 @@ function getSchemaExtension(schema, key) {
|
|
|
983
1100
|
return schema[key];
|
|
984
1101
|
}
|
|
985
1102
|
|
|
1103
|
+
// src/extensions/registry.ts
|
|
1104
|
+
function createExtensionRegistry(extensions) {
|
|
1105
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1106
|
+
const constraintMap = /* @__PURE__ */ new Map();
|
|
1107
|
+
const annotationMap = /* @__PURE__ */ new Map();
|
|
1108
|
+
for (const ext of extensions) {
|
|
1109
|
+
if (ext.types !== void 0) {
|
|
1110
|
+
for (const type of ext.types) {
|
|
1111
|
+
const qualifiedId = `${ext.extensionId}/${type.typeName}`;
|
|
1112
|
+
if (typeMap.has(qualifiedId)) {
|
|
1113
|
+
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1114
|
+
}
|
|
1115
|
+
typeMap.set(qualifiedId, type);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (ext.constraints !== void 0) {
|
|
1119
|
+
for (const constraint of ext.constraints) {
|
|
1120
|
+
const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
|
|
1121
|
+
if (constraintMap.has(qualifiedId)) {
|
|
1122
|
+
throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
|
|
1123
|
+
}
|
|
1124
|
+
constraintMap.set(qualifiedId, constraint);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
if (ext.annotations !== void 0) {
|
|
1128
|
+
for (const annotation of ext.annotations) {
|
|
1129
|
+
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
1130
|
+
if (annotationMap.has(qualifiedId)) {
|
|
1131
|
+
throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
|
|
1132
|
+
}
|
|
1133
|
+
annotationMap.set(qualifiedId, annotation);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
extensions,
|
|
1139
|
+
findType: (typeId) => typeMap.get(typeId),
|
|
1140
|
+
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1141
|
+
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
986
1145
|
// src/json-schema/schema.ts
|
|
987
1146
|
var import_zod3 = require("zod");
|
|
988
1147
|
var jsonSchemaTypeSchema = import_zod3.z.enum([
|
|
@@ -1122,7 +1281,6 @@ var ts4 = __toESM(require("typescript"), 1);
|
|
|
1122
1281
|
|
|
1123
1282
|
// src/analyzer/jsdoc-constraints.ts
|
|
1124
1283
|
var ts3 = __toESM(require("typescript"), 1);
|
|
1125
|
-
var import_core4 = require("@formspec/core");
|
|
1126
1284
|
|
|
1127
1285
|
// src/analyzer/tsdoc-parser.ts
|
|
1128
1286
|
var ts2 = __toESM(require("typescript"), 1);
|
|
@@ -1152,7 +1310,7 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
1152
1310
|
minItems: "minItems",
|
|
1153
1311
|
maxItems: "maxItems"
|
|
1154
1312
|
};
|
|
1155
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1313
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1156
1314
|
function createFormSpecTSDocConfig() {
|
|
1157
1315
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
1158
1316
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1164,6 +1322,15 @@ function createFormSpecTSDocConfig() {
|
|
|
1164
1322
|
})
|
|
1165
1323
|
);
|
|
1166
1324
|
}
|
|
1325
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
1326
|
+
config.addTagDefinition(
|
|
1327
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
1328
|
+
tagName: "@" + tagName,
|
|
1329
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
1330
|
+
allowMultiple: true
|
|
1331
|
+
})
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1167
1334
|
return config;
|
|
1168
1335
|
}
|
|
1169
1336
|
var sharedParser;
|
|
@@ -1174,6 +1341,12 @@ function getParser() {
|
|
|
1174
1341
|
function parseTSDocTags(node, file = "") {
|
|
1175
1342
|
const constraints = [];
|
|
1176
1343
|
const annotations = [];
|
|
1344
|
+
let displayName;
|
|
1345
|
+
let description;
|
|
1346
|
+
let placeholder;
|
|
1347
|
+
let displayNameProvenance;
|
|
1348
|
+
let descriptionProvenance;
|
|
1349
|
+
let placeholderProvenance;
|
|
1177
1350
|
const sourceFile = node.getSourceFile();
|
|
1178
1351
|
const sourceText = sourceFile.getFullText();
|
|
1179
1352
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -1193,9 +1366,37 @@ function parseTSDocTags(node, file = "") {
|
|
|
1193
1366
|
const docComment = parserContext.docComment;
|
|
1194
1367
|
for (const block of docComment.customBlocks) {
|
|
1195
1368
|
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
1369
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1370
|
+
const text2 = extractBlockText(block).trim();
|
|
1371
|
+
if (text2 === "") continue;
|
|
1372
|
+
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1373
|
+
if (tagName === "displayName") {
|
|
1374
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1375
|
+
displayName = text2;
|
|
1376
|
+
displayNameProvenance = provenance2;
|
|
1377
|
+
}
|
|
1378
|
+
} else if (tagName === "format") {
|
|
1379
|
+
annotations.push({
|
|
1380
|
+
kind: "annotation",
|
|
1381
|
+
annotationKind: "format",
|
|
1382
|
+
value: text2,
|
|
1383
|
+
provenance: provenance2
|
|
1384
|
+
});
|
|
1385
|
+
} else {
|
|
1386
|
+
if (tagName === "description" && description === void 0) {
|
|
1387
|
+
description = text2;
|
|
1388
|
+
descriptionProvenance = provenance2;
|
|
1389
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
1390
|
+
placeholder = text2;
|
|
1391
|
+
placeholderProvenance = provenance2;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1196
1396
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1197
1397
|
const text = extractBlockText(block).trim();
|
|
1198
|
-
|
|
1398
|
+
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1399
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
1199
1400
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1200
1401
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1201
1402
|
if (constraintNode) {
|
|
@@ -1203,14 +1404,47 @@ function parseTSDocTags(node, file = "") {
|
|
|
1203
1404
|
}
|
|
1204
1405
|
}
|
|
1205
1406
|
if (docComment.deprecatedBlock !== void 0) {
|
|
1407
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
1206
1408
|
annotations.push({
|
|
1207
1409
|
kind: "annotation",
|
|
1208
1410
|
annotationKind: "deprecated",
|
|
1411
|
+
...message !== "" && { message },
|
|
1209
1412
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1210
1413
|
});
|
|
1211
1414
|
}
|
|
1415
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
1416
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
1417
|
+
if (remarks !== "") {
|
|
1418
|
+
description = remarks;
|
|
1419
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1212
1422
|
}
|
|
1213
1423
|
}
|
|
1424
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
1425
|
+
annotations.push({
|
|
1426
|
+
kind: "annotation",
|
|
1427
|
+
annotationKind: "displayName",
|
|
1428
|
+
value: displayName,
|
|
1429
|
+
provenance: displayNameProvenance
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
1433
|
+
annotations.push({
|
|
1434
|
+
kind: "annotation",
|
|
1435
|
+
annotationKind: "description",
|
|
1436
|
+
value: description,
|
|
1437
|
+
provenance: descriptionProvenance
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
1441
|
+
annotations.push({
|
|
1442
|
+
kind: "annotation",
|
|
1443
|
+
annotationKind: "placeholder",
|
|
1444
|
+
value: placeholder,
|
|
1445
|
+
provenance: placeholderProvenance
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1214
1448
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1215
1449
|
for (const tag of jsDocTagsAll) {
|
|
1216
1450
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
@@ -1219,47 +1453,39 @@ function parseTSDocTags(node, file = "") {
|
|
|
1219
1453
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
1220
1454
|
const text = commentText.trim();
|
|
1221
1455
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
1456
|
+
if (tagName === "defaultValue") {
|
|
1457
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
1458
|
+
annotations.push(defaultValueNode);
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1222
1461
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1223
1462
|
if (constraintNode) {
|
|
1224
1463
|
constraints.push(constraintNode);
|
|
1225
1464
|
}
|
|
1226
1465
|
}
|
|
1466
|
+
return { constraints, annotations };
|
|
1467
|
+
}
|
|
1468
|
+
function extractDisplayNameMetadata(node) {
|
|
1227
1469
|
let displayName;
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
const tagName = tag.tagName.text;
|
|
1470
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1471
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
1472
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1473
|
+
if (tagName !== "displayName") continue;
|
|
1233
1474
|
const commentText = getTagCommentText(tag);
|
|
1234
|
-
if (commentText === void 0
|
|
1475
|
+
if (commentText === void 0) continue;
|
|
1476
|
+
const text = commentText.trim();
|
|
1477
|
+
if (text === "") continue;
|
|
1478
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
1479
|
+
if (memberTarget) {
|
|
1480
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
1235
1481
|
continue;
|
|
1236
1482
|
}
|
|
1237
|
-
|
|
1238
|
-
if (tagName === "Field_displayName") {
|
|
1239
|
-
displayName = trimmed;
|
|
1240
|
-
displayNameTag = tag;
|
|
1241
|
-
} else if (tagName === "Field_description") {
|
|
1242
|
-
description = trimmed;
|
|
1243
|
-
descriptionTag = tag;
|
|
1244
|
-
}
|
|
1483
|
+
displayName ??= text;
|
|
1245
1484
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
value: displayName,
|
|
1251
|
-
provenance: provenanceForJSDocTag(displayNameTag, file)
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
if (description !== void 0 && descriptionTag) {
|
|
1255
|
-
annotations.push({
|
|
1256
|
-
kind: "annotation",
|
|
1257
|
-
annotationKind: "description",
|
|
1258
|
-
value: description,
|
|
1259
|
-
provenance: provenanceForJSDocTag(descriptionTag, file)
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
return { constraints, annotations };
|
|
1485
|
+
return {
|
|
1486
|
+
...displayName !== void 0 && { displayName },
|
|
1487
|
+
memberDisplayNames
|
|
1488
|
+
};
|
|
1263
1489
|
}
|
|
1264
1490
|
function extractPathTarget(text) {
|
|
1265
1491
|
const trimmed = text.trimStart();
|
|
@@ -1323,7 +1549,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1323
1549
|
}
|
|
1324
1550
|
return null;
|
|
1325
1551
|
}
|
|
1552
|
+
if (expectedType === "boolean") {
|
|
1553
|
+
const trimmed = effectiveText.trim();
|
|
1554
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
1555
|
+
return null;
|
|
1556
|
+
}
|
|
1557
|
+
if (tagName === "uniqueItems") {
|
|
1558
|
+
return {
|
|
1559
|
+
kind: "constraint",
|
|
1560
|
+
constraintKind: "uniqueItems",
|
|
1561
|
+
value: true,
|
|
1562
|
+
...path3 && { path: path3 },
|
|
1563
|
+
provenance
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
return null;
|
|
1567
|
+
}
|
|
1326
1568
|
if (expectedType === "json") {
|
|
1569
|
+
if (tagName === "const") {
|
|
1570
|
+
const trimmedText = effectiveText.trim();
|
|
1571
|
+
if (trimmedText === "") return null;
|
|
1572
|
+
try {
|
|
1573
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
1574
|
+
return {
|
|
1575
|
+
kind: "constraint",
|
|
1576
|
+
constraintKind: "const",
|
|
1577
|
+
value: parsed2,
|
|
1578
|
+
...path3 && { path: path3 },
|
|
1579
|
+
provenance
|
|
1580
|
+
};
|
|
1581
|
+
} catch {
|
|
1582
|
+
return {
|
|
1583
|
+
kind: "constraint",
|
|
1584
|
+
constraintKind: "const",
|
|
1585
|
+
value: trimmedText,
|
|
1586
|
+
...path3 && { path: path3 },
|
|
1587
|
+
provenance
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1327
1591
|
const parsed = tryParseJson(effectiveText);
|
|
1328
1592
|
if (!Array.isArray(parsed)) {
|
|
1329
1593
|
return null;
|
|
@@ -1355,6 +1619,34 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1355
1619
|
provenance
|
|
1356
1620
|
};
|
|
1357
1621
|
}
|
|
1622
|
+
function parseDefaultValueValue(text, provenance) {
|
|
1623
|
+
const trimmed = text.trim();
|
|
1624
|
+
let value;
|
|
1625
|
+
if (trimmed === "null") {
|
|
1626
|
+
value = null;
|
|
1627
|
+
} else if (trimmed === "true") {
|
|
1628
|
+
value = true;
|
|
1629
|
+
} else if (trimmed === "false") {
|
|
1630
|
+
value = false;
|
|
1631
|
+
} else {
|
|
1632
|
+
const parsed = tryParseJson(trimmed);
|
|
1633
|
+
value = parsed !== null ? parsed : trimmed;
|
|
1634
|
+
}
|
|
1635
|
+
return {
|
|
1636
|
+
kind: "annotation",
|
|
1637
|
+
annotationKind: "defaultValue",
|
|
1638
|
+
value,
|
|
1639
|
+
provenance
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function isMemberTargetDisplayName(text) {
|
|
1643
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
1644
|
+
}
|
|
1645
|
+
function parseMemberTargetDisplayName(text) {
|
|
1646
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1647
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1648
|
+
return { target: match[1], label: match[2].trim() };
|
|
1649
|
+
}
|
|
1358
1650
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1359
1651
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1360
1652
|
return {
|
|
@@ -1436,11 +1728,17 @@ function isObjectType(type) {
|
|
|
1436
1728
|
function isTypeReference(type) {
|
|
1437
1729
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
1438
1730
|
}
|
|
1731
|
+
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1732
|
+
kind: "object",
|
|
1733
|
+
properties: [],
|
|
1734
|
+
additionalProperties: true
|
|
1735
|
+
};
|
|
1439
1736
|
function analyzeClassToIR(classDecl, checker, file = "") {
|
|
1440
1737
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1441
1738
|
const fields = [];
|
|
1442
1739
|
const fieldLayouts = [];
|
|
1443
1740
|
const typeRegistry = {};
|
|
1741
|
+
const annotations = extractJSDocAnnotationNodes(classDecl, file);
|
|
1444
1742
|
const visiting = /* @__PURE__ */ new Set();
|
|
1445
1743
|
const instanceMethods = [];
|
|
1446
1744
|
const staticMethods = [];
|
|
@@ -1463,12 +1761,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1463
1761
|
}
|
|
1464
1762
|
}
|
|
1465
1763
|
}
|
|
1466
|
-
return {
|
|
1764
|
+
return {
|
|
1765
|
+
name,
|
|
1766
|
+
fields,
|
|
1767
|
+
fieldLayouts,
|
|
1768
|
+
typeRegistry,
|
|
1769
|
+
...annotations.length > 0 && { annotations },
|
|
1770
|
+
instanceMethods,
|
|
1771
|
+
staticMethods
|
|
1772
|
+
};
|
|
1467
1773
|
}
|
|
1468
1774
|
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1469
1775
|
const name = interfaceDecl.name.text;
|
|
1470
1776
|
const fields = [];
|
|
1471
1777
|
const typeRegistry = {};
|
|
1778
|
+
const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
|
|
1472
1779
|
const visiting = /* @__PURE__ */ new Set();
|
|
1473
1780
|
for (const member of interfaceDecl.members) {
|
|
1474
1781
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -1479,7 +1786,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
1479
1786
|
}
|
|
1480
1787
|
}
|
|
1481
1788
|
const fieldLayouts = fields.map(() => ({}));
|
|
1482
|
-
return {
|
|
1789
|
+
return {
|
|
1790
|
+
name,
|
|
1791
|
+
fields,
|
|
1792
|
+
fieldLayouts,
|
|
1793
|
+
typeRegistry,
|
|
1794
|
+
...annotations.length > 0 && { annotations },
|
|
1795
|
+
instanceMethods: [],
|
|
1796
|
+
staticMethods: []
|
|
1797
|
+
};
|
|
1483
1798
|
}
|
|
1484
1799
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1485
1800
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
@@ -1494,6 +1809,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1494
1809
|
const name = typeAlias.name.text;
|
|
1495
1810
|
const fields = [];
|
|
1496
1811
|
const typeRegistry = {};
|
|
1812
|
+
const annotations = extractJSDocAnnotationNodes(typeAlias, file);
|
|
1497
1813
|
const visiting = /* @__PURE__ */ new Set();
|
|
1498
1814
|
for (const member of typeAlias.type.members) {
|
|
1499
1815
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -1510,6 +1826,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1510
1826
|
fields,
|
|
1511
1827
|
fieldLayouts: fields.map(() => ({})),
|
|
1512
1828
|
typeRegistry,
|
|
1829
|
+
...annotations.length > 0 && { annotations },
|
|
1513
1830
|
instanceMethods: [],
|
|
1514
1831
|
staticMethods: []
|
|
1515
1832
|
}
|
|
@@ -1523,18 +1840,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1523
1840
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1524
1841
|
const optional = prop.questionToken !== void 0;
|
|
1525
1842
|
const provenance = provenanceForNode(prop, file);
|
|
1526
|
-
|
|
1843
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
1527
1844
|
const constraints = [];
|
|
1528
1845
|
if (prop.type) {
|
|
1529
1846
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1530
1847
|
}
|
|
1531
1848
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1532
|
-
|
|
1849
|
+
let annotations = [];
|
|
1533
1850
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1534
1851
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1535
|
-
if (defaultAnnotation) {
|
|
1852
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1536
1853
|
annotations.push(defaultAnnotation);
|
|
1537
1854
|
}
|
|
1855
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1538
1856
|
return {
|
|
1539
1857
|
kind: "field",
|
|
1540
1858
|
name,
|
|
@@ -1553,14 +1871,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1553
1871
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1554
1872
|
const optional = prop.questionToken !== void 0;
|
|
1555
1873
|
const provenance = provenanceForNode(prop, file);
|
|
1556
|
-
|
|
1874
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
1557
1875
|
const constraints = [];
|
|
1558
1876
|
if (prop.type) {
|
|
1559
1877
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1560
1878
|
}
|
|
1561
1879
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1562
|
-
|
|
1880
|
+
let annotations = [];
|
|
1563
1881
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1882
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1564
1883
|
return {
|
|
1565
1884
|
kind: "field",
|
|
1566
1885
|
name,
|
|
@@ -1571,7 +1890,69 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1571
1890
|
provenance
|
|
1572
1891
|
};
|
|
1573
1892
|
}
|
|
1574
|
-
function
|
|
1893
|
+
function applyEnumMemberDisplayNames(type, annotations) {
|
|
1894
|
+
if (!annotations.some(
|
|
1895
|
+
(annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
|
|
1896
|
+
)) {
|
|
1897
|
+
return { type, annotations: [...annotations] };
|
|
1898
|
+
}
|
|
1899
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
1900
|
+
const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
|
|
1901
|
+
if (consumed.size === 0) {
|
|
1902
|
+
return { type, annotations: [...annotations] };
|
|
1903
|
+
}
|
|
1904
|
+
return {
|
|
1905
|
+
type: nextType,
|
|
1906
|
+
annotations: annotations.filter((annotation) => !consumed.has(annotation))
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
function rewriteEnumDisplayNames(type, annotations, consumed) {
|
|
1910
|
+
switch (type.kind) {
|
|
1911
|
+
case "enum":
|
|
1912
|
+
return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
|
|
1913
|
+
case "union": {
|
|
1914
|
+
return {
|
|
1915
|
+
...type,
|
|
1916
|
+
members: type.members.map(
|
|
1917
|
+
(member) => rewriteEnumDisplayNames(member, annotations, consumed)
|
|
1918
|
+
)
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
default:
|
|
1922
|
+
return type;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
|
|
1926
|
+
const displayNames = /* @__PURE__ */ new Map();
|
|
1927
|
+
for (const annotation of annotations) {
|
|
1928
|
+
if (annotation.annotationKind !== "displayName") continue;
|
|
1929
|
+
const parsed = parseEnumMemberDisplayName(annotation.value);
|
|
1930
|
+
if (!parsed) continue;
|
|
1931
|
+
consumed.add(annotation);
|
|
1932
|
+
const member = type.members.find((m) => String(m.value) === parsed.value);
|
|
1933
|
+
if (!member) continue;
|
|
1934
|
+
displayNames.set(String(member.value), parsed.label);
|
|
1935
|
+
}
|
|
1936
|
+
if (displayNames.size === 0) {
|
|
1937
|
+
return type;
|
|
1938
|
+
}
|
|
1939
|
+
return {
|
|
1940
|
+
...type,
|
|
1941
|
+
members: type.members.map((member) => {
|
|
1942
|
+
const displayName = displayNames.get(String(member.value));
|
|
1943
|
+
return displayName !== void 0 ? { ...member, displayName } : member;
|
|
1944
|
+
})
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
function parseEnumMemberDisplayName(value) {
|
|
1948
|
+
const trimmed = value.trim();
|
|
1949
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
|
|
1950
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1951
|
+
const label = match[2].trim();
|
|
1952
|
+
if (label === "") return null;
|
|
1953
|
+
return { value: match[1], label };
|
|
1954
|
+
}
|
|
1955
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1575
1956
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1576
1957
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1577
1958
|
}
|
|
@@ -1600,7 +1981,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1600
1981
|
};
|
|
1601
1982
|
}
|
|
1602
1983
|
if (type.isUnion()) {
|
|
1603
|
-
return resolveUnionType(type, checker, file, typeRegistry, visiting);
|
|
1984
|
+
return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
|
|
1604
1985
|
}
|
|
1605
1986
|
if (checker.isArrayType(type)) {
|
|
1606
1987
|
return resolveArrayType(type, checker, file, typeRegistry, visiting);
|
|
@@ -1610,70 +1991,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1610
1991
|
}
|
|
1611
1992
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1612
1993
|
}
|
|
1613
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
1994
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1995
|
+
const typeName = getNamedTypeName(type);
|
|
1996
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1997
|
+
if (typeName && typeName in typeRegistry) {
|
|
1998
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1999
|
+
}
|
|
1614
2000
|
const allTypes = type.types;
|
|
1615
2001
|
const nonNullTypes = allTypes.filter(
|
|
1616
2002
|
(t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1617
2003
|
);
|
|
1618
2004
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
2005
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2006
|
+
if (namedDecl) {
|
|
2007
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
2008
|
+
memberDisplayNames.set(value, label);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
if (sourceNode) {
|
|
2012
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
2013
|
+
memberDisplayNames.set(value, label);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
const registerNamed = (result) => {
|
|
2017
|
+
if (!typeName) {
|
|
2018
|
+
return result;
|
|
2019
|
+
}
|
|
2020
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2021
|
+
typeRegistry[typeName] = {
|
|
2022
|
+
name: typeName,
|
|
2023
|
+
type: result,
|
|
2024
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2025
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
2026
|
+
};
|
|
2027
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2028
|
+
};
|
|
2029
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
2030
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
2031
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2032
|
+
});
|
|
1619
2033
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1620
2034
|
if (isBooleanUnion2) {
|
|
1621
2035
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
}
|
|
1628
|
-
return boolNode;
|
|
2036
|
+
const result = hasNull ? {
|
|
2037
|
+
kind: "union",
|
|
2038
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2039
|
+
} : boolNode;
|
|
2040
|
+
return registerNamed(result);
|
|
1629
2041
|
}
|
|
1630
2042
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1631
2043
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1632
2044
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1633
2045
|
const enumNode = {
|
|
1634
2046
|
kind: "enum",
|
|
1635
|
-
members: stringTypes.map((t) =>
|
|
2047
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1636
2048
|
};
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
}
|
|
1643
|
-
return enumNode;
|
|
2049
|
+
const result = hasNull ? {
|
|
2050
|
+
kind: "union",
|
|
2051
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2052
|
+
} : enumNode;
|
|
2053
|
+
return registerNamed(result);
|
|
1644
2054
|
}
|
|
1645
2055
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1646
2056
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1647
2057
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1648
2058
|
const enumNode = {
|
|
1649
2059
|
kind: "enum",
|
|
1650
|
-
members: numberTypes.map((t) =>
|
|
2060
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1651
2061
|
};
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
}
|
|
1658
|
-
return enumNode;
|
|
2062
|
+
const result = hasNull ? {
|
|
2063
|
+
kind: "union",
|
|
2064
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2065
|
+
} : enumNode;
|
|
2066
|
+
return registerNamed(result);
|
|
1659
2067
|
}
|
|
1660
2068
|
if (nonNullTypes.length === 1 && nonNullTypes[0]) {
|
|
1661
|
-
const inner = resolveTypeNode(
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
2069
|
+
const inner = resolveTypeNode(
|
|
2070
|
+
nonNullTypes[0],
|
|
2071
|
+
checker,
|
|
2072
|
+
file,
|
|
2073
|
+
typeRegistry,
|
|
2074
|
+
visiting,
|
|
2075
|
+
sourceNode
|
|
2076
|
+
);
|
|
2077
|
+
const result = hasNull ? {
|
|
2078
|
+
kind: "union",
|
|
2079
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
2080
|
+
} : inner;
|
|
2081
|
+
return registerNamed(result);
|
|
1669
2082
|
}
|
|
1670
2083
|
const members = nonNullTypes.map(
|
|
1671
|
-
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
|
|
2084
|
+
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
|
|
1672
2085
|
);
|
|
1673
2086
|
if (hasNull) {
|
|
1674
2087
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1675
2088
|
}
|
|
1676
|
-
return { kind: "union", members };
|
|
2089
|
+
return registerNamed({ kind: "union", members });
|
|
1677
2090
|
}
|
|
1678
2091
|
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1679
2092
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
@@ -1681,15 +2094,92 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
|
1681
2094
|
const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
|
|
1682
2095
|
return { kind: "array", items };
|
|
1683
2096
|
}
|
|
2097
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
2098
|
+
if (type.getProperties().length > 0) {
|
|
2099
|
+
return null;
|
|
2100
|
+
}
|
|
2101
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
|
|
2102
|
+
if (!indexInfo) {
|
|
2103
|
+
return null;
|
|
2104
|
+
}
|
|
2105
|
+
const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
|
|
2106
|
+
return { kind: "record", valueType };
|
|
2107
|
+
}
|
|
2108
|
+
function typeNodeContainsReference(type, targetName) {
|
|
2109
|
+
switch (type.kind) {
|
|
2110
|
+
case "reference":
|
|
2111
|
+
return type.name === targetName;
|
|
2112
|
+
case "array":
|
|
2113
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
2114
|
+
case "record":
|
|
2115
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
2116
|
+
case "union":
|
|
2117
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
2118
|
+
case "object":
|
|
2119
|
+
return type.properties.some(
|
|
2120
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
2121
|
+
);
|
|
2122
|
+
case "primitive":
|
|
2123
|
+
case "enum":
|
|
2124
|
+
case "dynamic":
|
|
2125
|
+
case "custom":
|
|
2126
|
+
return false;
|
|
2127
|
+
default: {
|
|
2128
|
+
const _exhaustive = type;
|
|
2129
|
+
return _exhaustive;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
1684
2133
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
2134
|
+
const typeName = getNamedTypeName(type);
|
|
2135
|
+
const namedTypeName = typeName ?? void 0;
|
|
2136
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2137
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2138
|
+
const clearNamedTypeRegistration = () => {
|
|
2139
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
2143
|
+
};
|
|
1685
2144
|
if (visiting.has(type)) {
|
|
2145
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2146
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2147
|
+
}
|
|
1686
2148
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1687
2149
|
}
|
|
2150
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
2151
|
+
typeRegistry[namedTypeName] = {
|
|
2152
|
+
name: namedTypeName,
|
|
2153
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2154
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
1688
2157
|
visiting.add(type);
|
|
1689
|
-
|
|
1690
|
-
|
|
2158
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
2159
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2160
|
+
visiting.delete(type);
|
|
2161
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
2165
|
+
if (recordNode) {
|
|
1691
2166
|
visiting.delete(type);
|
|
1692
|
-
|
|
2167
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2168
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
2169
|
+
if (!isRecursiveRecord) {
|
|
2170
|
+
clearNamedTypeRegistration();
|
|
2171
|
+
return recordNode;
|
|
2172
|
+
}
|
|
2173
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2174
|
+
typeRegistry[namedTypeName] = {
|
|
2175
|
+
name: namedTypeName,
|
|
2176
|
+
type: recordNode,
|
|
2177
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2178
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2179
|
+
};
|
|
2180
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2181
|
+
}
|
|
2182
|
+
return recordNode;
|
|
1693
2183
|
}
|
|
1694
2184
|
const properties = [];
|
|
1695
2185
|
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
|
|
@@ -1698,7 +2188,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1698
2188
|
if (!declaration) continue;
|
|
1699
2189
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1700
2190
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1701
|
-
const propTypeNode = resolveTypeNode(
|
|
2191
|
+
const propTypeNode = resolveTypeNode(
|
|
2192
|
+
propType,
|
|
2193
|
+
checker,
|
|
2194
|
+
file,
|
|
2195
|
+
typeRegistry,
|
|
2196
|
+
visiting,
|
|
2197
|
+
declaration
|
|
2198
|
+
);
|
|
1702
2199
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1703
2200
|
properties.push({
|
|
1704
2201
|
name: prop.name,
|
|
@@ -1713,15 +2210,17 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1713
2210
|
const objectNode = {
|
|
1714
2211
|
kind: "object",
|
|
1715
2212
|
properties,
|
|
1716
|
-
additionalProperties:
|
|
2213
|
+
additionalProperties: true
|
|
1717
2214
|
};
|
|
1718
|
-
if (
|
|
1719
|
-
|
|
1720
|
-
|
|
2215
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2216
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2217
|
+
typeRegistry[namedTypeName] = {
|
|
2218
|
+
name: namedTypeName,
|
|
1721
2219
|
type: objectNode,
|
|
1722
|
-
|
|
2220
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2221
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1723
2222
|
};
|
|
1724
|
-
return { kind: "reference", name:
|
|
2223
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1725
2224
|
}
|
|
1726
2225
|
return objectNode;
|
|
1727
2226
|
}
|
|
@@ -1813,6 +2312,12 @@ function provenanceForNode(node, file) {
|
|
|
1813
2312
|
function provenanceForFile(file) {
|
|
1814
2313
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1815
2314
|
}
|
|
2315
|
+
function provenanceForDeclaration(node, file) {
|
|
2316
|
+
if (!node) {
|
|
2317
|
+
return provenanceForFile(file);
|
|
2318
|
+
}
|
|
2319
|
+
return provenanceForNode(node, file);
|
|
2320
|
+
}
|
|
1816
2321
|
function getNamedTypeName(type) {
|
|
1817
2322
|
const symbol = type.getSymbol();
|
|
1818
2323
|
if (symbol?.declarations) {
|
|
@@ -1831,6 +2336,20 @@ function getNamedTypeName(type) {
|
|
|
1831
2336
|
}
|
|
1832
2337
|
return null;
|
|
1833
2338
|
}
|
|
2339
|
+
function getNamedTypeDeclaration(type) {
|
|
2340
|
+
const symbol = type.getSymbol();
|
|
2341
|
+
if (symbol?.declarations) {
|
|
2342
|
+
const decl = symbol.declarations[0];
|
|
2343
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
2344
|
+
return decl;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
const aliasSymbol = type.aliasSymbol;
|
|
2348
|
+
if (aliasSymbol?.declarations) {
|
|
2349
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2350
|
+
}
|
|
2351
|
+
return void 0;
|
|
2352
|
+
}
|
|
1834
2353
|
function analyzeMethod(method, checker) {
|
|
1835
2354
|
if (!ts4.isIdentifier(method.name)) {
|
|
1836
2355
|
return null;
|
|
@@ -1915,16 +2434,219 @@ function generateSchemas(options) {
|
|
|
1915
2434
|
);
|
|
1916
2435
|
}
|
|
1917
2436
|
|
|
2437
|
+
// src/generators/mixed-authoring.ts
|
|
2438
|
+
function buildMixedAuthoringSchemas(options) {
|
|
2439
|
+
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2440
|
+
const analysis = analyzeNamedType(filePath, typeName);
|
|
2441
|
+
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2442
|
+
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2443
|
+
return {
|
|
2444
|
+
jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
|
|
2445
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
function analyzeNamedType(filePath, typeName) {
|
|
2449
|
+
const ctx = createProgramContext(filePath);
|
|
2450
|
+
const source = { file: filePath };
|
|
2451
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2452
|
+
if (classDecl !== null) {
|
|
2453
|
+
return analyzeClassToIR(classDecl, ctx.checker, source.file);
|
|
2454
|
+
}
|
|
2455
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2456
|
+
if (interfaceDecl !== null) {
|
|
2457
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
|
|
2458
|
+
}
|
|
2459
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2460
|
+
if (typeAlias !== null) {
|
|
2461
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
|
|
2462
|
+
if (result.ok) {
|
|
2463
|
+
return result.analysis;
|
|
2464
|
+
}
|
|
2465
|
+
throw new Error(result.error);
|
|
2466
|
+
}
|
|
2467
|
+
throw new Error(
|
|
2468
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2469
|
+
);
|
|
2470
|
+
}
|
|
2471
|
+
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2472
|
+
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2473
|
+
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
2474
|
+
if (overlayFields.length === 0) {
|
|
2475
|
+
return analysis;
|
|
2476
|
+
}
|
|
2477
|
+
const overlayByName = /* @__PURE__ */ new Map();
|
|
2478
|
+
for (const field of overlayFields) {
|
|
2479
|
+
if (overlayByName.has(field.name)) {
|
|
2480
|
+
throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
|
|
2481
|
+
}
|
|
2482
|
+
overlayByName.set(field.name, field);
|
|
2483
|
+
}
|
|
2484
|
+
const mergedFields = [];
|
|
2485
|
+
for (const baseField of analysis.fields) {
|
|
2486
|
+
const overlayField = overlayByName.get(baseField.name);
|
|
2487
|
+
if (overlayField === void 0) {
|
|
2488
|
+
mergedFields.push(baseField);
|
|
2489
|
+
continue;
|
|
2490
|
+
}
|
|
2491
|
+
mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
|
|
2492
|
+
overlayByName.delete(baseField.name);
|
|
2493
|
+
}
|
|
2494
|
+
if (overlayByName.size > 0) {
|
|
2495
|
+
const unknownFields = [...overlayByName.keys()].sort().join(", ");
|
|
2496
|
+
throw new Error(
|
|
2497
|
+
`Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
return {
|
|
2501
|
+
...analysis,
|
|
2502
|
+
fields: mergedFields
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
function collectOverlayFields(elements) {
|
|
2506
|
+
const fields = [];
|
|
2507
|
+
for (const element of elements) {
|
|
2508
|
+
switch (element.kind) {
|
|
2509
|
+
case "field":
|
|
2510
|
+
fields.push(element);
|
|
2511
|
+
break;
|
|
2512
|
+
case "group":
|
|
2513
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2514
|
+
break;
|
|
2515
|
+
case "conditional":
|
|
2516
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2517
|
+
break;
|
|
2518
|
+
default: {
|
|
2519
|
+
const _exhaustive = element;
|
|
2520
|
+
void _exhaustive;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
return fields;
|
|
2525
|
+
}
|
|
2526
|
+
function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
|
|
2527
|
+
assertSupportedOverlayField(baseField, overlayField);
|
|
2528
|
+
return {
|
|
2529
|
+
...baseField,
|
|
2530
|
+
type: mergeFieldType(baseField, overlayField, typeRegistry),
|
|
2531
|
+
annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
function assertSupportedOverlayField(baseField, overlayField) {
|
|
2535
|
+
if (overlayField.constraints.length > 0) {
|
|
2536
|
+
throw new Error(
|
|
2537
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
if (overlayField.required) {
|
|
2541
|
+
throw new Error(
|
|
2542
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
function mergeFieldType(baseField, overlayField, typeRegistry) {
|
|
2547
|
+
const { type: baseType } = baseField;
|
|
2548
|
+
const { type: overlayType } = overlayField;
|
|
2549
|
+
if (overlayType.kind === "object" || overlayType.kind === "array") {
|
|
2550
|
+
throw new Error(
|
|
2551
|
+
`Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
if (overlayType.kind === "dynamic") {
|
|
2555
|
+
if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
|
|
2556
|
+
throw new Error(
|
|
2557
|
+
`Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
return overlayType;
|
|
2561
|
+
}
|
|
2562
|
+
if (!isSameStaticTypeShape(baseType, overlayType)) {
|
|
2563
|
+
throw new Error(
|
|
2564
|
+
`Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
return baseType;
|
|
2568
|
+
}
|
|
2569
|
+
function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
|
|
2570
|
+
const overlayType = overlayField.type;
|
|
2571
|
+
if (overlayType.kind !== "dynamic") {
|
|
2572
|
+
return false;
|
|
2573
|
+
}
|
|
2574
|
+
const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
|
|
2575
|
+
if (resolvedBaseType === null) {
|
|
2576
|
+
return false;
|
|
2577
|
+
}
|
|
2578
|
+
if (overlayType.dynamicKind === "enum") {
|
|
2579
|
+
return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
|
|
2580
|
+
}
|
|
2581
|
+
return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
|
|
2582
|
+
}
|
|
2583
|
+
function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
|
|
2584
|
+
if (type.kind !== "reference") {
|
|
2585
|
+
return type;
|
|
2586
|
+
}
|
|
2587
|
+
if (seen.has(type.name)) {
|
|
2588
|
+
return null;
|
|
2589
|
+
}
|
|
2590
|
+
const definition = typeRegistry[type.name];
|
|
2591
|
+
if (definition === void 0) {
|
|
2592
|
+
return null;
|
|
2593
|
+
}
|
|
2594
|
+
seen.add(type.name);
|
|
2595
|
+
return resolveReferenceType(definition.type, typeRegistry, seen);
|
|
2596
|
+
}
|
|
2597
|
+
function isSameStaticTypeShape(baseType, overlayType) {
|
|
2598
|
+
if (baseType.kind !== overlayType.kind) {
|
|
2599
|
+
return false;
|
|
2600
|
+
}
|
|
2601
|
+
switch (baseType.kind) {
|
|
2602
|
+
case "primitive":
|
|
2603
|
+
return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
|
|
2604
|
+
case "enum":
|
|
2605
|
+
return overlayType.kind === "enum";
|
|
2606
|
+
case "dynamic":
|
|
2607
|
+
return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
|
|
2608
|
+
case "record":
|
|
2609
|
+
return overlayType.kind === "record";
|
|
2610
|
+
case "reference":
|
|
2611
|
+
return overlayType.kind === "reference" && baseType.name === overlayType.name;
|
|
2612
|
+
case "union":
|
|
2613
|
+
return overlayType.kind === "union";
|
|
2614
|
+
case "custom":
|
|
2615
|
+
return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
|
|
2616
|
+
case "object":
|
|
2617
|
+
case "array":
|
|
2618
|
+
return true;
|
|
2619
|
+
default: {
|
|
2620
|
+
const _exhaustive = baseType;
|
|
2621
|
+
return _exhaustive;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
2626
|
+
const baseKeys = new Set(baseAnnotations.map(annotationKey));
|
|
2627
|
+
const overlayOnly = overlayAnnotations.filter(
|
|
2628
|
+
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
2629
|
+
);
|
|
2630
|
+
return [...overlayOnly, ...baseAnnotations];
|
|
2631
|
+
}
|
|
2632
|
+
function annotationKey(annotation) {
|
|
2633
|
+
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
2634
|
+
}
|
|
2635
|
+
|
|
1918
2636
|
// src/index.ts
|
|
1919
|
-
function buildFormSchemas(form) {
|
|
2637
|
+
function buildFormSchemas(form, options) {
|
|
1920
2638
|
return {
|
|
1921
|
-
jsonSchema: generateJsonSchema(form),
|
|
2639
|
+
jsonSchema: generateJsonSchema(form, options),
|
|
1922
2640
|
uiSchema: generateUiSchema(form)
|
|
1923
2641
|
};
|
|
1924
2642
|
}
|
|
1925
2643
|
function writeSchemas(form, options) {
|
|
1926
|
-
const { outDir, name = "schema", indent = 2 } = options;
|
|
1927
|
-
const
|
|
2644
|
+
const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
|
|
2645
|
+
const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
|
|
2646
|
+
extensionRegistry,
|
|
2647
|
+
vendorPrefix
|
|
2648
|
+
};
|
|
2649
|
+
const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
|
|
1928
2650
|
if (!fs.existsSync(outDir)) {
|
|
1929
2651
|
fs.mkdirSync(outDir, { recursive: true });
|
|
1930
2652
|
}
|
|
@@ -1937,10 +2659,13 @@ function writeSchemas(form, options) {
|
|
|
1937
2659
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1938
2660
|
0 && (module.exports = {
|
|
1939
2661
|
buildFormSchemas,
|
|
2662
|
+
buildMixedAuthoringSchemas,
|
|
1940
2663
|
categorizationSchema,
|
|
1941
2664
|
categorySchema,
|
|
1942
2665
|
controlSchema,
|
|
2666
|
+
createExtensionRegistry,
|
|
1943
2667
|
generateJsonSchema,
|
|
2668
|
+
generateJsonSchemaFromIR,
|
|
1944
2669
|
generateSchemas,
|
|
1945
2670
|
generateSchemasFromClass,
|
|
1946
2671
|
generateUiSchema,
|