@formspec/build 0.1.0-alpha.13 → 0.1.0-alpha.15
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/README.md +20 -20
- package/dist/__tests__/alias-chain-propagation.test.d.ts +9 -0
- package/dist/__tests__/alias-chain-propagation.test.d.ts.map +1 -0
- 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/alias-chains.d.ts +37 -0
- package/dist/__tests__/fixtures/alias-chains.d.ts.map +1 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +13 -13
- package/dist/__tests__/fixtures/example-interface-types.d.ts +33 -33
- package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -1
- package/dist/__tests__/jsdoc-constraints.test.d.ts +4 -5
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +1 -1
- package/dist/__tests__/json-utils.test.d.ts +5 -0
- package/dist/__tests__/json-utils.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 +6 -1
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/__tests__/path-target-parser.test.d.ts +9 -0
- package/dist/__tests__/path-target-parser.test.d.ts.map +1 -0
- package/dist/analyzer/class-analyzer.d.ts +1 -1
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +8 -52
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/json-utils.d.ts +22 -0
- package/dist/analyzer/json-utils.d.ts.map +1 -0
- package/dist/analyzer/tsdoc-parser.d.ts +24 -12
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +452 -94
- 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 +450 -94
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +132 -5
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
- package/dist/cli.cjs +406 -104
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +407 -104
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +386 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +20 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +386 -104
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +597 -172
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +597 -172
- 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 +25 -2
- 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/validate/constraint-validator.d.ts +3 -7
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -177,7 +177,7 @@ function canonicalizeArrayField(field) {
|
|
|
177
177
|
const itemsType = {
|
|
178
178
|
kind: "object",
|
|
179
179
|
properties: itemProperties,
|
|
180
|
-
additionalProperties:
|
|
180
|
+
additionalProperties: true
|
|
181
181
|
};
|
|
182
182
|
const type = { kind: "array", items: itemsType };
|
|
183
183
|
const constraints = [];
|
|
@@ -212,7 +212,7 @@ function canonicalizeObjectField(field) {
|
|
|
212
212
|
const type = {
|
|
213
213
|
kind: "object",
|
|
214
214
|
properties,
|
|
215
|
-
additionalProperties:
|
|
215
|
+
additionalProperties: true
|
|
216
216
|
};
|
|
217
217
|
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
218
218
|
}
|
|
@@ -384,11 +384,21 @@ function wrapInConditional(field, layout, provenance) {
|
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
// src/json-schema/ir-generator.ts
|
|
387
|
-
function makeContext() {
|
|
388
|
-
|
|
387
|
+
function makeContext(options) {
|
|
388
|
+
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
389
|
+
if (!vendorPrefix.startsWith("x-")) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
`Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
defs: {},
|
|
396
|
+
extensionRegistry: options?.extensionRegistry,
|
|
397
|
+
vendorPrefix
|
|
398
|
+
};
|
|
389
399
|
}
|
|
390
|
-
function generateJsonSchemaFromIR(ir) {
|
|
391
|
-
const ctx = makeContext();
|
|
400
|
+
function generateJsonSchemaFromIR(ir, options) {
|
|
401
|
+
const ctx = makeContext(options);
|
|
392
402
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
393
403
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
394
404
|
}
|
|
@@ -431,8 +441,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
431
441
|
}
|
|
432
442
|
function generateFieldSchema(field, ctx) {
|
|
433
443
|
const schema = generateTypeNode(field.type, ctx);
|
|
434
|
-
|
|
435
|
-
|
|
444
|
+
const directConstraints = [];
|
|
445
|
+
const pathConstraints = [];
|
|
446
|
+
for (const c of field.constraints) {
|
|
447
|
+
if (c.path) {
|
|
448
|
+
pathConstraints.push(c);
|
|
449
|
+
} else {
|
|
450
|
+
directConstraints.push(c);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
454
|
+
applyAnnotations(schema, field.annotations, ctx);
|
|
455
|
+
if (pathConstraints.length === 0) {
|
|
456
|
+
return schema;
|
|
457
|
+
}
|
|
458
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
459
|
+
}
|
|
460
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
461
|
+
if (schema.type === "array" && schema.items) {
|
|
462
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
463
|
+
return schema;
|
|
464
|
+
}
|
|
465
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
466
|
+
for (const c of pathConstraints) {
|
|
467
|
+
const target = c.path?.segments[0];
|
|
468
|
+
if (!target) continue;
|
|
469
|
+
const group = byTarget.get(target) ?? [];
|
|
470
|
+
group.push(c);
|
|
471
|
+
byTarget.set(target, group);
|
|
472
|
+
}
|
|
473
|
+
const propertyOverrides = {};
|
|
474
|
+
for (const [target, constraints] of byTarget) {
|
|
475
|
+
const subSchema = {};
|
|
476
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
477
|
+
propertyOverrides[target] = subSchema;
|
|
478
|
+
}
|
|
479
|
+
if (schema.$ref) {
|
|
480
|
+
const { $ref, ...rest } = schema;
|
|
481
|
+
const refPart = { $ref };
|
|
482
|
+
const overridePart = {
|
|
483
|
+
properties: propertyOverrides,
|
|
484
|
+
...rest
|
|
485
|
+
};
|
|
486
|
+
return { allOf: [refPart, overridePart] };
|
|
487
|
+
}
|
|
488
|
+
if (schema.type === "object" && schema.properties) {
|
|
489
|
+
const missingOverrides = {};
|
|
490
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
491
|
+
if (schema.properties[target]) {
|
|
492
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
493
|
+
} else {
|
|
494
|
+
missingOverrides[target] = overrideSchema;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
498
|
+
return schema;
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
if (schema.allOf) {
|
|
505
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
506
|
+
return schema;
|
|
507
|
+
}
|
|
436
508
|
return schema;
|
|
437
509
|
}
|
|
438
510
|
function generateTypeNode(type, ctx) {
|
|
@@ -445,6 +517,8 @@ function generateTypeNode(type, ctx) {
|
|
|
445
517
|
return generateArrayType(type, ctx);
|
|
446
518
|
case "object":
|
|
447
519
|
return generateObjectType(type, ctx);
|
|
520
|
+
case "record":
|
|
521
|
+
return generateRecordType(type, ctx);
|
|
448
522
|
case "union":
|
|
449
523
|
return generateUnionType(type, ctx);
|
|
450
524
|
case "reference":
|
|
@@ -452,7 +526,7 @@ function generateTypeNode(type, ctx) {
|
|
|
452
526
|
case "dynamic":
|
|
453
527
|
return generateDynamicType(type);
|
|
454
528
|
case "custom":
|
|
455
|
-
return generateCustomType(type);
|
|
529
|
+
return generateCustomType(type, ctx);
|
|
456
530
|
default: {
|
|
457
531
|
const _exhaustive = type;
|
|
458
532
|
return _exhaustive;
|
|
@@ -501,16 +575,27 @@ function generateObjectType(type, ctx) {
|
|
|
501
575
|
}
|
|
502
576
|
return schema;
|
|
503
577
|
}
|
|
578
|
+
function generateRecordType(type, ctx) {
|
|
579
|
+
return {
|
|
580
|
+
type: "object",
|
|
581
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
582
|
+
};
|
|
583
|
+
}
|
|
504
584
|
function generatePropertySchema(prop, ctx) {
|
|
505
585
|
const schema = generateTypeNode(prop.type, ctx);
|
|
506
|
-
applyConstraints(schema, prop.constraints);
|
|
507
|
-
applyAnnotations(schema, prop.annotations);
|
|
586
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
587
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
508
588
|
return schema;
|
|
509
589
|
}
|
|
510
590
|
function generateUnionType(type, ctx) {
|
|
511
591
|
if (isBooleanUnion(type)) {
|
|
512
592
|
return { type: "boolean" };
|
|
513
593
|
}
|
|
594
|
+
if (isNullableUnion(type)) {
|
|
595
|
+
return {
|
|
596
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
597
|
+
};
|
|
598
|
+
}
|
|
514
599
|
return {
|
|
515
600
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
516
601
|
};
|
|
@@ -520,6 +605,13 @@ function isBooleanUnion(type) {
|
|
|
520
605
|
const kinds = type.members.map((m) => m.kind);
|
|
521
606
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
522
607
|
}
|
|
608
|
+
function isNullableUnion(type) {
|
|
609
|
+
if (type.members.length !== 2) return false;
|
|
610
|
+
const nullCount = type.members.filter(
|
|
611
|
+
(m) => m.kind === "primitive" && m.primitiveKind === "null"
|
|
612
|
+
).length;
|
|
613
|
+
return nullCount === 1;
|
|
614
|
+
}
|
|
523
615
|
function generateReferenceType(type) {
|
|
524
616
|
return { $ref: `#/$defs/${type.name}` };
|
|
525
617
|
}
|
|
@@ -540,10 +632,7 @@ function generateDynamicType(type) {
|
|
|
540
632
|
"x-formspec-schemaSource": type.sourceKey
|
|
541
633
|
};
|
|
542
634
|
}
|
|
543
|
-
function
|
|
544
|
-
return { type: "object" };
|
|
545
|
-
}
|
|
546
|
-
function applyConstraints(schema, constraints) {
|
|
635
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
547
636
|
for (const constraint of constraints) {
|
|
548
637
|
switch (constraint.constraintKind) {
|
|
549
638
|
case "minimum":
|
|
@@ -588,6 +677,7 @@ function applyConstraints(schema, constraints) {
|
|
|
588
677
|
case "allowedMembers":
|
|
589
678
|
break;
|
|
590
679
|
case "custom":
|
|
680
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
591
681
|
break;
|
|
592
682
|
default: {
|
|
593
683
|
const _exhaustive = constraint;
|
|
@@ -596,7 +686,7 @@ function applyConstraints(schema, constraints) {
|
|
|
596
686
|
}
|
|
597
687
|
}
|
|
598
688
|
}
|
|
599
|
-
function applyAnnotations(schema, annotations) {
|
|
689
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
600
690
|
for (const annotation of annotations) {
|
|
601
691
|
switch (annotation.annotationKind) {
|
|
602
692
|
case "displayName":
|
|
@@ -616,6 +706,7 @@ function applyAnnotations(schema, annotations) {
|
|
|
616
706
|
case "formatHint":
|
|
617
707
|
break;
|
|
618
708
|
case "custom":
|
|
709
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
619
710
|
break;
|
|
620
711
|
default: {
|
|
621
712
|
const _exhaustive = annotation;
|
|
@@ -624,11 +715,41 @@ function applyAnnotations(schema, annotations) {
|
|
|
624
715
|
}
|
|
625
716
|
}
|
|
626
717
|
}
|
|
718
|
+
function generateCustomType(type, ctx) {
|
|
719
|
+
const registration = ctx.extensionRegistry?.findType(type.typeId);
|
|
720
|
+
if (registration === void 0) {
|
|
721
|
+
throw new Error(
|
|
722
|
+
`Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
|
|
726
|
+
}
|
|
727
|
+
function applyCustomConstraint(schema, constraint, ctx) {
|
|
728
|
+
const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
|
|
729
|
+
if (registration === void 0) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
|
|
735
|
+
}
|
|
736
|
+
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
737
|
+
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
738
|
+
if (registration === void 0) {
|
|
739
|
+
throw new Error(
|
|
740
|
+
`Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
if (registration.toJsonSchema === void 0) {
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
|
|
747
|
+
}
|
|
627
748
|
|
|
628
749
|
// src/json-schema/generator.ts
|
|
629
|
-
function generateJsonSchema(form) {
|
|
750
|
+
function generateJsonSchema(form, options) {
|
|
630
751
|
const ir = canonicalizeChainDSL(form);
|
|
631
|
-
return generateJsonSchemaFromIR(ir);
|
|
752
|
+
return generateJsonSchemaFromIR(ir, options);
|
|
632
753
|
}
|
|
633
754
|
|
|
634
755
|
// src/ui-schema/schema.ts
|
|
@@ -862,6 +983,48 @@ function getSchemaExtension(schema, key) {
|
|
|
862
983
|
return schema[key];
|
|
863
984
|
}
|
|
864
985
|
|
|
986
|
+
// src/extensions/registry.ts
|
|
987
|
+
function createExtensionRegistry(extensions) {
|
|
988
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
989
|
+
const constraintMap = /* @__PURE__ */ new Map();
|
|
990
|
+
const annotationMap = /* @__PURE__ */ new Map();
|
|
991
|
+
for (const ext of extensions) {
|
|
992
|
+
if (ext.types !== void 0) {
|
|
993
|
+
for (const type of ext.types) {
|
|
994
|
+
const qualifiedId = `${ext.extensionId}/${type.typeName}`;
|
|
995
|
+
if (typeMap.has(qualifiedId)) {
|
|
996
|
+
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
997
|
+
}
|
|
998
|
+
typeMap.set(qualifiedId, type);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (ext.constraints !== void 0) {
|
|
1002
|
+
for (const constraint of ext.constraints) {
|
|
1003
|
+
const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
|
|
1004
|
+
if (constraintMap.has(qualifiedId)) {
|
|
1005
|
+
throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
|
|
1006
|
+
}
|
|
1007
|
+
constraintMap.set(qualifiedId, constraint);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
if (ext.annotations !== void 0) {
|
|
1011
|
+
for (const annotation of ext.annotations) {
|
|
1012
|
+
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
1013
|
+
if (annotationMap.has(qualifiedId)) {
|
|
1014
|
+
throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
|
|
1015
|
+
}
|
|
1016
|
+
annotationMap.set(qualifiedId, annotation);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return {
|
|
1021
|
+
extensions,
|
|
1022
|
+
findType: (typeId) => typeMap.get(typeId),
|
|
1023
|
+
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1024
|
+
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
865
1028
|
// src/json-schema/schema.ts
|
|
866
1029
|
import { z as z3 } from "zod";
|
|
867
1030
|
var jsonSchemaTypeSchema = z3.enum([
|
|
@@ -1001,9 +1164,6 @@ import * as ts4 from "typescript";
|
|
|
1001
1164
|
|
|
1002
1165
|
// src/analyzer/jsdoc-constraints.ts
|
|
1003
1166
|
import * as ts3 from "typescript";
|
|
1004
|
-
import {
|
|
1005
|
-
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2
|
|
1006
|
-
} from "@formspec/core";
|
|
1007
1167
|
|
|
1008
1168
|
// src/analyzer/tsdoc-parser.ts
|
|
1009
1169
|
import * as ts2 from "typescript";
|
|
@@ -1017,22 +1177,35 @@ import {
|
|
|
1017
1177
|
TextRange
|
|
1018
1178
|
} from "@microsoft/tsdoc";
|
|
1019
1179
|
import {
|
|
1020
|
-
BUILTIN_CONSTRAINT_DEFINITIONS
|
|
1180
|
+
BUILTIN_CONSTRAINT_DEFINITIONS,
|
|
1181
|
+
normalizeConstraintTagName,
|
|
1182
|
+
isBuiltinConstraintName
|
|
1021
1183
|
} from "@formspec/core";
|
|
1184
|
+
|
|
1185
|
+
// src/analyzer/json-utils.ts
|
|
1186
|
+
function tryParseJson(text) {
|
|
1187
|
+
try {
|
|
1188
|
+
return JSON.parse(text);
|
|
1189
|
+
} catch {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// src/analyzer/tsdoc-parser.ts
|
|
1022
1195
|
var NUMERIC_CONSTRAINT_MAP = {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1196
|
+
minimum: "minimum",
|
|
1197
|
+
maximum: "maximum",
|
|
1198
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
1199
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
1200
|
+
multipleOf: "multipleOf"
|
|
1027
1201
|
};
|
|
1028
1202
|
var LENGTH_CONSTRAINT_MAP = {
|
|
1029
|
-
|
|
1030
|
-
|
|
1203
|
+
minLength: "minLength",
|
|
1204
|
+
maxLength: "maxLength",
|
|
1205
|
+
minItems: "minItems",
|
|
1206
|
+
maxItems: "maxItems"
|
|
1031
1207
|
};
|
|
1032
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
1033
|
-
function isBuiltinConstraintName(tagName) {
|
|
1034
|
-
return tagName in BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
1035
|
-
}
|
|
1208
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1036
1209
|
function createFormSpecTSDocConfig() {
|
|
1037
1210
|
const config = new TSDocConfiguration();
|
|
1038
1211
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1044,6 +1217,15 @@ function createFormSpecTSDocConfig() {
|
|
|
1044
1217
|
})
|
|
1045
1218
|
);
|
|
1046
1219
|
}
|
|
1220
|
+
for (const tagName of ["displayName", "description"]) {
|
|
1221
|
+
config.addTagDefinition(
|
|
1222
|
+
new TSDocTagDefinition({
|
|
1223
|
+
tagName: "@" + tagName,
|
|
1224
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
1225
|
+
allowMultiple: true
|
|
1226
|
+
})
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1047
1229
|
return config;
|
|
1048
1230
|
}
|
|
1049
1231
|
var sharedParser;
|
|
@@ -1072,7 +1254,28 @@ function parseTSDocTags(node, file = "") {
|
|
|
1072
1254
|
);
|
|
1073
1255
|
const docComment = parserContext.docComment;
|
|
1074
1256
|
for (const block of docComment.customBlocks) {
|
|
1075
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
1257
|
+
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1258
|
+
if (tagName === "displayName" || tagName === "description") {
|
|
1259
|
+
const text2 = extractBlockText(block).trim();
|
|
1260
|
+
if (text2 === "") continue;
|
|
1261
|
+
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1262
|
+
if (tagName === "displayName") {
|
|
1263
|
+
annotations.push({
|
|
1264
|
+
kind: "annotation",
|
|
1265
|
+
annotationKind: "displayName",
|
|
1266
|
+
value: text2,
|
|
1267
|
+
provenance: provenance2
|
|
1268
|
+
});
|
|
1269
|
+
} else {
|
|
1270
|
+
annotations.push({
|
|
1271
|
+
kind: "annotation",
|
|
1272
|
+
annotationKind: "description",
|
|
1273
|
+
value: text2,
|
|
1274
|
+
provenance: provenance2
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1076
1279
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1077
1280
|
const text = extractBlockText(block).trim();
|
|
1078
1281
|
if (text === "") continue;
|
|
@@ -1093,7 +1296,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1093
1296
|
}
|
|
1094
1297
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1095
1298
|
for (const tag of jsDocTagsAll) {
|
|
1096
|
-
const tagName = tag.tagName.text;
|
|
1299
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1097
1300
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1098
1301
|
const commentText = getTagCommentText(tag);
|
|
1099
1302
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -1104,43 +1307,17 @@ function parseTSDocTags(node, file = "") {
|
|
|
1104
1307
|
constraints.push(constraintNode);
|
|
1105
1308
|
}
|
|
1106
1309
|
}
|
|
1107
|
-
let displayName;
|
|
1108
|
-
let description;
|
|
1109
|
-
let displayNameTag;
|
|
1110
|
-
let descriptionTag;
|
|
1111
|
-
for (const tag of jsDocTagsAll) {
|
|
1112
|
-
const tagName = tag.tagName.text;
|
|
1113
|
-
const commentText = getTagCommentText(tag);
|
|
1114
|
-
if (commentText === void 0 || commentText.trim() === "") {
|
|
1115
|
-
continue;
|
|
1116
|
-
}
|
|
1117
|
-
const trimmed = commentText.trim();
|
|
1118
|
-
if (tagName === "Field_displayName") {
|
|
1119
|
-
displayName = trimmed;
|
|
1120
|
-
displayNameTag = tag;
|
|
1121
|
-
} else if (tagName === "Field_description") {
|
|
1122
|
-
description = trimmed;
|
|
1123
|
-
descriptionTag = tag;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
if (displayName !== void 0 && displayNameTag) {
|
|
1127
|
-
annotations.push({
|
|
1128
|
-
kind: "annotation",
|
|
1129
|
-
annotationKind: "displayName",
|
|
1130
|
-
value: displayName,
|
|
1131
|
-
provenance: provenanceForJSDocTag(displayNameTag, file)
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
if (description !== void 0 && descriptionTag) {
|
|
1135
|
-
annotations.push({
|
|
1136
|
-
kind: "annotation",
|
|
1137
|
-
annotationKind: "description",
|
|
1138
|
-
value: description,
|
|
1139
|
-
provenance: provenanceForJSDocTag(descriptionTag, file)
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
1310
|
return { constraints, annotations };
|
|
1143
1311
|
}
|
|
1312
|
+
function extractPathTarget(text) {
|
|
1313
|
+
const trimmed = text.trimStart();
|
|
1314
|
+
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
1315
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1316
|
+
return {
|
|
1317
|
+
path: { segments: [match[1]] },
|
|
1318
|
+
remainingText: match[2]
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1144
1321
|
function extractBlockText(block) {
|
|
1145
1322
|
return extractPlainText(block.content);
|
|
1146
1323
|
}
|
|
@@ -1163,9 +1340,12 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1163
1340
|
if (!isBuiltinConstraintName(tagName)) {
|
|
1164
1341
|
return null;
|
|
1165
1342
|
}
|
|
1343
|
+
const pathResult = extractPathTarget(text);
|
|
1344
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1345
|
+
const path3 = pathResult?.path;
|
|
1166
1346
|
const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1167
1347
|
if (expectedType === "number") {
|
|
1168
|
-
const value = Number(
|
|
1348
|
+
const value = Number(effectiveText);
|
|
1169
1349
|
if (Number.isNaN(value)) {
|
|
1170
1350
|
return null;
|
|
1171
1351
|
}
|
|
@@ -1175,6 +1355,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1175
1355
|
kind: "constraint",
|
|
1176
1356
|
constraintKind: numericKind,
|
|
1177
1357
|
value,
|
|
1358
|
+
...path3 && { path: path3 },
|
|
1178
1359
|
provenance
|
|
1179
1360
|
};
|
|
1180
1361
|
}
|
|
@@ -1184,42 +1365,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1184
1365
|
kind: "constraint",
|
|
1185
1366
|
constraintKind: lengthKind,
|
|
1186
1367
|
value,
|
|
1368
|
+
...path3 && { path: path3 },
|
|
1187
1369
|
provenance
|
|
1188
1370
|
};
|
|
1189
1371
|
}
|
|
1190
1372
|
return null;
|
|
1191
1373
|
}
|
|
1192
1374
|
if (expectedType === "json") {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
members.push(id);
|
|
1206
|
-
}
|
|
1375
|
+
const parsed = tryParseJson(effectiveText);
|
|
1376
|
+
if (!Array.isArray(parsed)) {
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1379
|
+
const members = [];
|
|
1380
|
+
for (const item of parsed) {
|
|
1381
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
1382
|
+
members.push(item);
|
|
1383
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
1384
|
+
const id = item["id"];
|
|
1385
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
1386
|
+
members.push(id);
|
|
1207
1387
|
}
|
|
1208
1388
|
}
|
|
1209
|
-
return {
|
|
1210
|
-
kind: "constraint",
|
|
1211
|
-
constraintKind: "allowedMembers",
|
|
1212
|
-
members,
|
|
1213
|
-
provenance
|
|
1214
|
-
};
|
|
1215
|
-
} catch {
|
|
1216
|
-
return null;
|
|
1217
1389
|
}
|
|
1390
|
+
return {
|
|
1391
|
+
kind: "constraint",
|
|
1392
|
+
constraintKind: "allowedMembers",
|
|
1393
|
+
members,
|
|
1394
|
+
...path3 && { path: path3 },
|
|
1395
|
+
provenance
|
|
1396
|
+
};
|
|
1218
1397
|
}
|
|
1219
1398
|
return {
|
|
1220
1399
|
kind: "constraint",
|
|
1221
1400
|
constraintKind: "pattern",
|
|
1222
|
-
pattern:
|
|
1401
|
+
pattern: effectiveText,
|
|
1402
|
+
...path3 && { path: path3 },
|
|
1223
1403
|
provenance
|
|
1224
1404
|
};
|
|
1225
1405
|
}
|
|
@@ -1391,18 +1571,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1391
1571
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1392
1572
|
const optional = prop.questionToken !== void 0;
|
|
1393
1573
|
const provenance = provenanceForNode(prop, file);
|
|
1394
|
-
|
|
1574
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1395
1575
|
const constraints = [];
|
|
1396
1576
|
if (prop.type) {
|
|
1397
1577
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1398
1578
|
}
|
|
1399
1579
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1400
|
-
|
|
1580
|
+
let annotations = [];
|
|
1401
1581
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1402
1582
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1403
1583
|
if (defaultAnnotation) {
|
|
1404
1584
|
annotations.push(defaultAnnotation);
|
|
1405
1585
|
}
|
|
1586
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1406
1587
|
return {
|
|
1407
1588
|
kind: "field",
|
|
1408
1589
|
name,
|
|
@@ -1421,14 +1602,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1421
1602
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1422
1603
|
const optional = prop.questionToken !== void 0;
|
|
1423
1604
|
const provenance = provenanceForNode(prop, file);
|
|
1424
|
-
|
|
1605
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1425
1606
|
const constraints = [];
|
|
1426
1607
|
if (prop.type) {
|
|
1427
1608
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1428
1609
|
}
|
|
1429
1610
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1430
|
-
|
|
1611
|
+
let annotations = [];
|
|
1431
1612
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1613
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1432
1614
|
return {
|
|
1433
1615
|
kind: "field",
|
|
1434
1616
|
name,
|
|
@@ -1439,6 +1621,68 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1439
1621
|
provenance
|
|
1440
1622
|
};
|
|
1441
1623
|
}
|
|
1624
|
+
function applyEnumMemberDisplayNames(type, annotations) {
|
|
1625
|
+
if (!annotations.some(
|
|
1626
|
+
(annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
|
|
1627
|
+
)) {
|
|
1628
|
+
return { type, annotations: [...annotations] };
|
|
1629
|
+
}
|
|
1630
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
1631
|
+
const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
|
|
1632
|
+
if (consumed.size === 0) {
|
|
1633
|
+
return { type, annotations: [...annotations] };
|
|
1634
|
+
}
|
|
1635
|
+
return {
|
|
1636
|
+
type: nextType,
|
|
1637
|
+
annotations: annotations.filter((annotation) => !consumed.has(annotation))
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
function rewriteEnumDisplayNames(type, annotations, consumed) {
|
|
1641
|
+
switch (type.kind) {
|
|
1642
|
+
case "enum":
|
|
1643
|
+
return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
|
|
1644
|
+
case "union": {
|
|
1645
|
+
return {
|
|
1646
|
+
...type,
|
|
1647
|
+
members: type.members.map(
|
|
1648
|
+
(member) => rewriteEnumDisplayNames(member, annotations, consumed)
|
|
1649
|
+
)
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
default:
|
|
1653
|
+
return type;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
|
|
1657
|
+
const displayNames = /* @__PURE__ */ new Map();
|
|
1658
|
+
for (const annotation of annotations) {
|
|
1659
|
+
if (annotation.annotationKind !== "displayName") continue;
|
|
1660
|
+
const parsed = parseEnumMemberDisplayName(annotation.value);
|
|
1661
|
+
if (!parsed) continue;
|
|
1662
|
+
consumed.add(annotation);
|
|
1663
|
+
const member = type.members.find((m) => String(m.value) === parsed.value);
|
|
1664
|
+
if (!member) continue;
|
|
1665
|
+
displayNames.set(String(member.value), parsed.label);
|
|
1666
|
+
}
|
|
1667
|
+
if (displayNames.size === 0) {
|
|
1668
|
+
return type;
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
...type,
|
|
1672
|
+
members: type.members.map((member) => {
|
|
1673
|
+
const displayName = displayNames.get(String(member.value));
|
|
1674
|
+
return displayName !== void 0 ? { ...member, displayName } : member;
|
|
1675
|
+
})
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function parseEnumMemberDisplayName(value) {
|
|
1679
|
+
const trimmed = value.trim();
|
|
1680
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
|
|
1681
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1682
|
+
const label = match[2].trim();
|
|
1683
|
+
if (label === "") return null;
|
|
1684
|
+
return { value: match[1], label };
|
|
1685
|
+
}
|
|
1442
1686
|
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
1443
1687
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1444
1688
|
return { kind: "primitive", primitiveKind: "string" };
|
|
@@ -1549,7 +1793,30 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
|
1549
1793
|
const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
|
|
1550
1794
|
return { kind: "array", items };
|
|
1551
1795
|
}
|
|
1796
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
1797
|
+
if (type.getProperties().length > 0) {
|
|
1798
|
+
return null;
|
|
1799
|
+
}
|
|
1800
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
|
|
1801
|
+
if (!indexInfo) {
|
|
1802
|
+
return null;
|
|
1803
|
+
}
|
|
1804
|
+
if (visiting.has(type)) {
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
visiting.add(type);
|
|
1808
|
+
try {
|
|
1809
|
+
const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
|
|
1810
|
+
return { kind: "record", valueType };
|
|
1811
|
+
} finally {
|
|
1812
|
+
visiting.delete(type);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1552
1815
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1816
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
1817
|
+
if (recordNode) {
|
|
1818
|
+
return recordNode;
|
|
1819
|
+
}
|
|
1553
1820
|
if (visiting.has(type)) {
|
|
1554
1821
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1555
1822
|
}
|
|
@@ -1581,7 +1848,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1581
1848
|
const objectNode = {
|
|
1582
1849
|
kind: "object",
|
|
1583
1850
|
properties,
|
|
1584
|
-
additionalProperties:
|
|
1851
|
+
additionalProperties: true
|
|
1585
1852
|
};
|
|
1586
1853
|
if (typeName) {
|
|
1587
1854
|
typeRegistry[typeName] = {
|
|
@@ -1650,14 +1917,23 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1650
1917
|
}
|
|
1651
1918
|
return map;
|
|
1652
1919
|
}
|
|
1653
|
-
|
|
1920
|
+
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1921
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1654
1922
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1923
|
+
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1924
|
+
const aliasName = typeNode.typeName.getText();
|
|
1925
|
+
throw new Error(
|
|
1926
|
+
`Type alias chain exceeds maximum depth of ${String(MAX_ALIAS_CHAIN_DEPTH)} at alias "${aliasName}" in ${file}. Simplify the alias chain or check for circular references.`
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1655
1929
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1656
1930
|
if (!symbol?.declarations) return [];
|
|
1657
1931
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1658
1932
|
if (!aliasDecl) return [];
|
|
1659
1933
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1660
|
-
|
|
1934
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1935
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1936
|
+
return constraints;
|
|
1661
1937
|
}
|
|
1662
1938
|
function provenanceForNode(node, file) {
|
|
1663
1939
|
const sourceFile = node.getSourceFile();
|
|
@@ -1775,15 +2051,19 @@ function generateSchemas(options) {
|
|
|
1775
2051
|
}
|
|
1776
2052
|
|
|
1777
2053
|
// src/index.ts
|
|
1778
|
-
function buildFormSchemas(form) {
|
|
2054
|
+
function buildFormSchemas(form, options) {
|
|
1779
2055
|
return {
|
|
1780
|
-
jsonSchema: generateJsonSchema(form),
|
|
2056
|
+
jsonSchema: generateJsonSchema(form, options),
|
|
1781
2057
|
uiSchema: generateUiSchema(form)
|
|
1782
2058
|
};
|
|
1783
2059
|
}
|
|
1784
2060
|
function writeSchemas(form, options) {
|
|
1785
|
-
const { outDir, name = "schema", indent = 2 } = options;
|
|
1786
|
-
const
|
|
2061
|
+
const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
|
|
2062
|
+
const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
|
|
2063
|
+
extensionRegistry,
|
|
2064
|
+
vendorPrefix
|
|
2065
|
+
};
|
|
2066
|
+
const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
|
|
1787
2067
|
if (!fs.existsSync(outDir)) {
|
|
1788
2068
|
fs.mkdirSync(outDir, { recursive: true });
|
|
1789
2069
|
}
|
|
@@ -1798,7 +2078,9 @@ export {
|
|
|
1798
2078
|
categorizationSchema,
|
|
1799
2079
|
categorySchema,
|
|
1800
2080
|
controlSchema,
|
|
2081
|
+
createExtensionRegistry,
|
|
1801
2082
|
generateJsonSchema,
|
|
2083
|
+
generateJsonSchemaFromIR,
|
|
1802
2084
|
generateSchemas,
|
|
1803
2085
|
generateSchemasFromClass,
|
|
1804
2086
|
generateUiSchema,
|