@formspec/build 0.1.0-alpha.14 → 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/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 +11 -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__/jsdoc-constraints.test.d.ts +4 -5
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +1 -1
- 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/analyzer/class-analyzer.d.ts +1 -1
- 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 +6 -8
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +387 -98
- 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 +385 -98
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +131 -5
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
- package/dist/cli.cjs +272 -69
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +271 -72
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +257 -67
- 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 +255 -71
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +461 -137
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +459 -139
- 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 +24 -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 +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -23,16 +23,26 @@
|
|
|
23
23
|
* @packageDocumentation
|
|
24
24
|
*/
|
|
25
25
|
import type { FormElement, FormSpec } from "@formspec/core";
|
|
26
|
-
import type
|
|
26
|
+
import { type GenerateJsonSchemaOptions } from "./json-schema/generator.js";
|
|
27
|
+
import { type GenerateJsonSchemaFromIROptions, type JsonSchema2020 } from "./json-schema/ir-generator.js";
|
|
27
28
|
import type { UISchema } from "./ui-schema/types.js";
|
|
28
29
|
export type { JsonSchema2020 } from "./json-schema/ir-generator.js";
|
|
30
|
+
export type { GenerateJsonSchemaFromIROptions } from "./json-schema/ir-generator.js";
|
|
31
|
+
export type { GenerateJsonSchemaOptions } from "./json-schema/generator.js";
|
|
29
32
|
export type { JSONSchema7, JSONSchemaType, ExtendedJSONSchema7, FormSpecSchemaExtensions, } from "./json-schema/types.js";
|
|
30
33
|
export { setSchemaExtension, getSchemaExtension } from "./json-schema/types.js";
|
|
34
|
+
export { createExtensionRegistry } from "./extensions/index.js";
|
|
35
|
+
export type { ExtensionRegistry } from "./extensions/index.js";
|
|
31
36
|
export type { UISchema, UISchemaElement, UISchemaElementBase, UISchemaElementType, ControlElement, VerticalLayout, HorizontalLayout, GroupLayout, Categorization, Category, LabelElement, Rule, RuleEffect, RuleConditionSchema, SchemaBasedCondition, } from "./ui-schema/types.js";
|
|
32
37
|
export type { ClassSchemas, GenerateFromClassOptions, GenerateFromClassResult, GenerateSchemasOptions, } from "./generators/class-schema.js";
|
|
33
38
|
export { ruleEffectSchema, uiSchemaElementTypeSchema, ruleConditionSchema, schemaBasedConditionSchema, ruleSchema, controlSchema, verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema, categorySchema, labelElementSchema, uiSchemaElementSchema, uiSchema as uiSchemaSchema, } from "./ui-schema/schema.js";
|
|
34
39
|
export { jsonSchemaTypeSchema, jsonSchema7Schema } from "./json-schema/schema.js";
|
|
35
40
|
export { generateJsonSchema } from "./json-schema/generator.js";
|
|
41
|
+
/**
|
|
42
|
+
* Advanced API — consumers are responsible for supplying canonical FormIR.
|
|
43
|
+
* Most callers should prefer `generateJsonSchema()` or `buildFormSchemas()`.
|
|
44
|
+
*/
|
|
45
|
+
export { generateJsonSchemaFromIR } from "./json-schema/ir-generator.js";
|
|
36
46
|
export { generateUiSchema } from "./ui-schema/generator.js";
|
|
37
47
|
export { generateSchemasFromClass, generateSchemas } from "./generators/class-schema.js";
|
|
38
48
|
/**
|
|
@@ -44,6 +54,13 @@ export interface BuildResult {
|
|
|
44
54
|
/** JSON Forms UI Schema for rendering */
|
|
45
55
|
readonly uiSchema: UISchema;
|
|
46
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Options for building schemas from a FormSpec.
|
|
59
|
+
*
|
|
60
|
+
* Currently identical to `GenerateJsonSchemaOptions`. Defined separately so the
|
|
61
|
+
* Chain DSL surface can grow independently in the future if needed.
|
|
62
|
+
*/
|
|
63
|
+
export type BuildFormSchemasOptions = GenerateJsonSchemaOptions;
|
|
47
64
|
/**
|
|
48
65
|
* Builds both JSON Schema and UI Schema from a FormSpec.
|
|
49
66
|
*
|
|
@@ -71,11 +88,11 @@ export interface BuildResult {
|
|
|
71
88
|
* @param form - The FormSpec to build schemas from
|
|
72
89
|
* @returns Object containing both jsonSchema and uiSchema
|
|
73
90
|
*/
|
|
74
|
-
export declare function buildFormSchemas<E extends readonly FormElement[]>(form: FormSpec<E
|
|
91
|
+
export declare function buildFormSchemas<E extends readonly FormElement[]>(form: FormSpec<E>, options?: BuildFormSchemasOptions): BuildResult;
|
|
75
92
|
/**
|
|
76
93
|
* Options for writing schemas to disk.
|
|
77
94
|
*/
|
|
78
|
-
export interface WriteSchemasOptions {
|
|
95
|
+
export interface WriteSchemasOptions extends GenerateJsonSchemaFromIROptions {
|
|
79
96
|
/** Output directory for the schema files */
|
|
80
97
|
readonly outDir: string;
|
|
81
98
|
/** Base name for the output files (without extension). Defaults to "schema" */
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
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
|
}
|
|
@@ -440,16 +450,16 @@ function generateFieldSchema(field, ctx) {
|
|
|
440
450
|
directConstraints.push(c);
|
|
441
451
|
}
|
|
442
452
|
}
|
|
443
|
-
applyConstraints(schema, directConstraints);
|
|
444
|
-
applyAnnotations(schema, field.annotations);
|
|
453
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
454
|
+
applyAnnotations(schema, field.annotations, ctx);
|
|
445
455
|
if (pathConstraints.length === 0) {
|
|
446
456
|
return schema;
|
|
447
457
|
}
|
|
448
|
-
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
458
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
449
459
|
}
|
|
450
|
-
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
460
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
451
461
|
if (schema.type === "array" && schema.items) {
|
|
452
|
-
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
462
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
453
463
|
return schema;
|
|
454
464
|
}
|
|
455
465
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -463,7 +473,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
|
463
473
|
const propertyOverrides = {};
|
|
464
474
|
for (const [target, constraints] of byTarget) {
|
|
465
475
|
const subSchema = {};
|
|
466
|
-
applyConstraints(subSchema, constraints);
|
|
476
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
467
477
|
propertyOverrides[target] = subSchema;
|
|
468
478
|
}
|
|
469
479
|
if (schema.$ref) {
|
|
@@ -507,6 +517,8 @@ function generateTypeNode(type, ctx) {
|
|
|
507
517
|
return generateArrayType(type, ctx);
|
|
508
518
|
case "object":
|
|
509
519
|
return generateObjectType(type, ctx);
|
|
520
|
+
case "record":
|
|
521
|
+
return generateRecordType(type, ctx);
|
|
510
522
|
case "union":
|
|
511
523
|
return generateUnionType(type, ctx);
|
|
512
524
|
case "reference":
|
|
@@ -514,7 +526,7 @@ function generateTypeNode(type, ctx) {
|
|
|
514
526
|
case "dynamic":
|
|
515
527
|
return generateDynamicType(type);
|
|
516
528
|
case "custom":
|
|
517
|
-
return generateCustomType(type);
|
|
529
|
+
return generateCustomType(type, ctx);
|
|
518
530
|
default: {
|
|
519
531
|
const _exhaustive = type;
|
|
520
532
|
return _exhaustive;
|
|
@@ -563,16 +575,27 @@ function generateObjectType(type, ctx) {
|
|
|
563
575
|
}
|
|
564
576
|
return schema;
|
|
565
577
|
}
|
|
578
|
+
function generateRecordType(type, ctx) {
|
|
579
|
+
return {
|
|
580
|
+
type: "object",
|
|
581
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
582
|
+
};
|
|
583
|
+
}
|
|
566
584
|
function generatePropertySchema(prop, ctx) {
|
|
567
585
|
const schema = generateTypeNode(prop.type, ctx);
|
|
568
|
-
applyConstraints(schema, prop.constraints);
|
|
569
|
-
applyAnnotations(schema, prop.annotations);
|
|
586
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
587
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
570
588
|
return schema;
|
|
571
589
|
}
|
|
572
590
|
function generateUnionType(type, ctx) {
|
|
573
591
|
if (isBooleanUnion(type)) {
|
|
574
592
|
return { type: "boolean" };
|
|
575
593
|
}
|
|
594
|
+
if (isNullableUnion(type)) {
|
|
595
|
+
return {
|
|
596
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
597
|
+
};
|
|
598
|
+
}
|
|
576
599
|
return {
|
|
577
600
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
578
601
|
};
|
|
@@ -582,6 +605,13 @@ function isBooleanUnion(type) {
|
|
|
582
605
|
const kinds = type.members.map((m) => m.kind);
|
|
583
606
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
584
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
|
+
}
|
|
585
615
|
function generateReferenceType(type) {
|
|
586
616
|
return { $ref: `#/$defs/${type.name}` };
|
|
587
617
|
}
|
|
@@ -602,10 +632,7 @@ function generateDynamicType(type) {
|
|
|
602
632
|
"x-formspec-schemaSource": type.sourceKey
|
|
603
633
|
};
|
|
604
634
|
}
|
|
605
|
-
function
|
|
606
|
-
return { type: "object" };
|
|
607
|
-
}
|
|
608
|
-
function applyConstraints(schema, constraints) {
|
|
635
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
609
636
|
for (const constraint of constraints) {
|
|
610
637
|
switch (constraint.constraintKind) {
|
|
611
638
|
case "minimum":
|
|
@@ -650,6 +677,7 @@ function applyConstraints(schema, constraints) {
|
|
|
650
677
|
case "allowedMembers":
|
|
651
678
|
break;
|
|
652
679
|
case "custom":
|
|
680
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
653
681
|
break;
|
|
654
682
|
default: {
|
|
655
683
|
const _exhaustive = constraint;
|
|
@@ -658,7 +686,7 @@ function applyConstraints(schema, constraints) {
|
|
|
658
686
|
}
|
|
659
687
|
}
|
|
660
688
|
}
|
|
661
|
-
function applyAnnotations(schema, annotations) {
|
|
689
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
662
690
|
for (const annotation of annotations) {
|
|
663
691
|
switch (annotation.annotationKind) {
|
|
664
692
|
case "displayName":
|
|
@@ -678,6 +706,7 @@ function applyAnnotations(schema, annotations) {
|
|
|
678
706
|
case "formatHint":
|
|
679
707
|
break;
|
|
680
708
|
case "custom":
|
|
709
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
681
710
|
break;
|
|
682
711
|
default: {
|
|
683
712
|
const _exhaustive = annotation;
|
|
@@ -686,11 +715,41 @@ function applyAnnotations(schema, annotations) {
|
|
|
686
715
|
}
|
|
687
716
|
}
|
|
688
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
|
+
}
|
|
689
748
|
|
|
690
749
|
// src/json-schema/generator.ts
|
|
691
|
-
function generateJsonSchema(form) {
|
|
750
|
+
function generateJsonSchema(form, options) {
|
|
692
751
|
const ir = canonicalizeChainDSL(form);
|
|
693
|
-
return generateJsonSchemaFromIR(ir);
|
|
752
|
+
return generateJsonSchemaFromIR(ir, options);
|
|
694
753
|
}
|
|
695
754
|
|
|
696
755
|
// src/ui-schema/schema.ts
|
|
@@ -924,6 +983,48 @@ function getSchemaExtension(schema, key) {
|
|
|
924
983
|
return schema[key];
|
|
925
984
|
}
|
|
926
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
|
+
|
|
927
1028
|
// src/json-schema/schema.ts
|
|
928
1029
|
import { z as z3 } from "zod";
|
|
929
1030
|
var jsonSchemaTypeSchema = z3.enum([
|
|
@@ -1063,11 +1164,6 @@ import * as ts4 from "typescript";
|
|
|
1063
1164
|
|
|
1064
1165
|
// src/analyzer/jsdoc-constraints.ts
|
|
1065
1166
|
import * as ts3 from "typescript";
|
|
1066
|
-
import {
|
|
1067
|
-
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
|
|
1068
|
-
isBuiltinConstraintName as isBuiltinConstraintName2,
|
|
1069
|
-
normalizeConstraintTagName as normalizeConstraintTagName2
|
|
1070
|
-
} from "@formspec/core";
|
|
1071
1167
|
|
|
1072
1168
|
// src/analyzer/tsdoc-parser.ts
|
|
1073
1169
|
import * as ts2 from "typescript";
|
|
@@ -1121,6 +1217,15 @@ function createFormSpecTSDocConfig() {
|
|
|
1121
1217
|
})
|
|
1122
1218
|
);
|
|
1123
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
|
+
}
|
|
1124
1229
|
return config;
|
|
1125
1230
|
}
|
|
1126
1231
|
var sharedParser;
|
|
@@ -1150,6 +1255,27 @@ function parseTSDocTags(node, file = "") {
|
|
|
1150
1255
|
const docComment = parserContext.docComment;
|
|
1151
1256
|
for (const block of docComment.customBlocks) {
|
|
1152
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
|
+
}
|
|
1153
1279
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1154
1280
|
const text = extractBlockText(block).trim();
|
|
1155
1281
|
if (text === "") continue;
|
|
@@ -1181,41 +1307,6 @@ function parseTSDocTags(node, file = "") {
|
|
|
1181
1307
|
constraints.push(constraintNode);
|
|
1182
1308
|
}
|
|
1183
1309
|
}
|
|
1184
|
-
let displayName;
|
|
1185
|
-
let description;
|
|
1186
|
-
let displayNameTag;
|
|
1187
|
-
let descriptionTag;
|
|
1188
|
-
for (const tag of jsDocTagsAll) {
|
|
1189
|
-
const tagName = tag.tagName.text;
|
|
1190
|
-
const commentText = getTagCommentText(tag);
|
|
1191
|
-
if (commentText === void 0 || commentText.trim() === "") {
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
const trimmed = commentText.trim();
|
|
1195
|
-
if (tagName === "Field_displayName") {
|
|
1196
|
-
displayName = trimmed;
|
|
1197
|
-
displayNameTag = tag;
|
|
1198
|
-
} else if (tagName === "Field_description") {
|
|
1199
|
-
description = trimmed;
|
|
1200
|
-
descriptionTag = tag;
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
if (displayName !== void 0 && displayNameTag) {
|
|
1204
|
-
annotations.push({
|
|
1205
|
-
kind: "annotation",
|
|
1206
|
-
annotationKind: "displayName",
|
|
1207
|
-
value: displayName,
|
|
1208
|
-
provenance: provenanceForJSDocTag(displayNameTag, file)
|
|
1209
|
-
});
|
|
1210
|
-
}
|
|
1211
|
-
if (description !== void 0 && descriptionTag) {
|
|
1212
|
-
annotations.push({
|
|
1213
|
-
kind: "annotation",
|
|
1214
|
-
annotationKind: "description",
|
|
1215
|
-
value: description,
|
|
1216
|
-
provenance: provenanceForJSDocTag(descriptionTag, file)
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
1310
|
return { constraints, annotations };
|
|
1220
1311
|
}
|
|
1221
1312
|
function extractPathTarget(text) {
|
|
@@ -1480,18 +1571,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1480
1571
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1481
1572
|
const optional = prop.questionToken !== void 0;
|
|
1482
1573
|
const provenance = provenanceForNode(prop, file);
|
|
1483
|
-
|
|
1574
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1484
1575
|
const constraints = [];
|
|
1485
1576
|
if (prop.type) {
|
|
1486
1577
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1487
1578
|
}
|
|
1488
1579
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1489
|
-
|
|
1580
|
+
let annotations = [];
|
|
1490
1581
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1491
1582
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1492
1583
|
if (defaultAnnotation) {
|
|
1493
1584
|
annotations.push(defaultAnnotation);
|
|
1494
1585
|
}
|
|
1586
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1495
1587
|
return {
|
|
1496
1588
|
kind: "field",
|
|
1497
1589
|
name,
|
|
@@ -1510,14 +1602,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1510
1602
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1511
1603
|
const optional = prop.questionToken !== void 0;
|
|
1512
1604
|
const provenance = provenanceForNode(prop, file);
|
|
1513
|
-
|
|
1605
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1514
1606
|
const constraints = [];
|
|
1515
1607
|
if (prop.type) {
|
|
1516
1608
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1517
1609
|
}
|
|
1518
1610
|
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1519
|
-
|
|
1611
|
+
let annotations = [];
|
|
1520
1612
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1613
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1521
1614
|
return {
|
|
1522
1615
|
kind: "field",
|
|
1523
1616
|
name,
|
|
@@ -1528,6 +1621,68 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1528
1621
|
provenance
|
|
1529
1622
|
};
|
|
1530
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
|
+
}
|
|
1531
1686
|
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
1532
1687
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1533
1688
|
return { kind: "primitive", primitiveKind: "string" };
|
|
@@ -1638,7 +1793,30 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
|
1638
1793
|
const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
|
|
1639
1794
|
return { kind: "array", items };
|
|
1640
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
|
+
}
|
|
1641
1815
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1816
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
1817
|
+
if (recordNode) {
|
|
1818
|
+
return recordNode;
|
|
1819
|
+
}
|
|
1642
1820
|
if (visiting.has(type)) {
|
|
1643
1821
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1644
1822
|
}
|
|
@@ -1670,7 +1848,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1670
1848
|
const objectNode = {
|
|
1671
1849
|
kind: "object",
|
|
1672
1850
|
properties,
|
|
1673
|
-
additionalProperties:
|
|
1851
|
+
additionalProperties: true
|
|
1674
1852
|
};
|
|
1675
1853
|
if (typeName) {
|
|
1676
1854
|
typeRegistry[typeName] = {
|
|
@@ -1873,15 +2051,19 @@ function generateSchemas(options) {
|
|
|
1873
2051
|
}
|
|
1874
2052
|
|
|
1875
2053
|
// src/index.ts
|
|
1876
|
-
function buildFormSchemas(form) {
|
|
2054
|
+
function buildFormSchemas(form, options) {
|
|
1877
2055
|
return {
|
|
1878
|
-
jsonSchema: generateJsonSchema(form),
|
|
2056
|
+
jsonSchema: generateJsonSchema(form, options),
|
|
1879
2057
|
uiSchema: generateUiSchema(form)
|
|
1880
2058
|
};
|
|
1881
2059
|
}
|
|
1882
2060
|
function writeSchemas(form, options) {
|
|
1883
|
-
const { outDir, name = "schema", indent = 2 } = options;
|
|
1884
|
-
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);
|
|
1885
2067
|
if (!fs.existsSync(outDir)) {
|
|
1886
2068
|
fs.mkdirSync(outDir, { recursive: true });
|
|
1887
2069
|
}
|
|
@@ -1896,7 +2078,9 @@ export {
|
|
|
1896
2078
|
categorizationSchema,
|
|
1897
2079
|
categorySchema,
|
|
1898
2080
|
controlSchema,
|
|
2081
|
+
createExtensionRegistry,
|
|
1899
2082
|
generateJsonSchema,
|
|
2083
|
+
generateJsonSchemaFromIR,
|
|
1900
2084
|
generateSchemas,
|
|
1901
2085
|
generateSchemasFromClass,
|
|
1902
2086
|
generateUiSchema,
|