@formspec/build 0.1.0-alpha.11 → 0.1.0-alpha.12
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 +51 -15
- package/dist/__tests__/chain-dsl-canonicalizer.test.d.ts +2 -0
- package/dist/__tests__/chain-dsl-canonicalizer.test.d.ts.map +1 -0
- package/dist/__tests__/constraint-validator.test.d.ts +2 -0
- package/dist/__tests__/constraint-validator.test.d.ts.map +1 -0
- package/dist/__tests__/extension-api.test.d.ts +2 -0
- package/dist/__tests__/extension-api.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +18 -0
- package/dist/__tests__/fixtures/example-a-builtins.d.ts.map +1 -1
- package/dist/__tests__/ir-analyzer.test.d.ts +11 -0
- package/dist/__tests__/ir-analyzer.test.d.ts.map +1 -0
- package/dist/__tests__/ir-jsdoc-constraints.test.d.ts +12 -0
- package/dist/__tests__/ir-jsdoc-constraints.test.d.ts.map +1 -0
- package/dist/__tests__/ir-json-schema-generator.test.d.ts +11 -0
- package/dist/__tests__/ir-json-schema-generator.test.d.ts.map +1 -0
- package/dist/__tests__/ir-ui-schema-generator.test.d.ts +2 -0
- package/dist/__tests__/ir-ui-schema-generator.test.d.ts.map +1 -0
- package/dist/__tests__/jsdoc-constraints.test.d.ts +4 -4
- package/dist/__tests__/parity/fixtures/address/chain-dsl.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/address/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/address/expected-ir.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/address/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/address/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/address/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/product-config/chain-dsl.d.ts +13 -0
- package/dist/__tests__/parity/fixtures/product-config/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/product-config/expected-ir.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/product-config/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/product-config/tsdoc.d.ts +28 -0
- package/dist/__tests__/parity/fixtures/product-config/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/user-registration/chain-dsl.d.ts +12 -0
- package/dist/__tests__/parity/fixtures/user-registration/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/user-registration/expected-ir.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/user-registration/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/user-registration/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/user-registration/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/parity.test.d.ts +14 -0
- package/dist/__tests__/parity/parity.test.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +139 -0
- package/dist/__tests__/parity/utils.d.ts.map +1 -0
- package/dist/analyzer/class-analyzer.d.ts +54 -99
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +78 -30
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +61 -0
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -0
- package/dist/browser.cjs +960 -309
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +10 -6
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +958 -308
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +65 -150
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +18 -0
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -0
- package/dist/canonicalize/index.d.ts +8 -0
- package/dist/canonicalize/index.d.ts.map +1 -0
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +34 -0
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -0
- package/dist/cli.cjs +1417 -1656
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1421 -1647
- package/dist/cli.js.map +1 -1
- package/dist/extensions/index.d.ts +8 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/registry.d.ts +55 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/generators/class-schema.d.ts +23 -38
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/method-schema.d.ts +6 -8
- package/dist/generators/method-schema.d.ts.map +1 -1
- package/dist/index.cjs +1353 -1614
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1365 -1610
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1604 -824
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.d.ts +12 -3
- package/dist/internals.d.ts.map +1 -1
- package/dist/internals.js +1607 -820
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/generator.d.ts +10 -5
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +84 -0
- package/dist/json-schema/ir-generator.d.ts.map +1 -0
- package/dist/json-schema/schema.d.ts +3 -3
- package/dist/json-schema/types.d.ts +5 -6
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/ui-schema/generator.d.ts +5 -15
- package/dist/ui-schema/generator.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts +53 -0
- package/dist/ui-schema/ir-generator.d.ts.map +1 -0
- package/dist/validate/constraint-validator.d.ts +66 -0
- package/dist/validate/constraint-validator.d.ts.map +1 -0
- package/dist/validate/index.d.ts +9 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/package.json +5 -4
- package/dist/__tests__/analyzer-edge-cases.test.d.ts +0 -13
- package/dist/__tests__/analyzer-edge-cases.test.d.ts.map +0 -1
- package/dist/__tests__/analyzer.test.d.ts +0 -5
- package/dist/__tests__/analyzer.test.d.ts.map +0 -1
- package/dist/__tests__/codegen.test.d.ts +0 -5
- package/dist/__tests__/codegen.test.d.ts.map +0 -1
- package/dist/__tests__/decorator-pipeline.test.d.ts +0 -11
- package/dist/__tests__/decorator-pipeline.test.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-b-decorators.d.ts +0 -5
- package/dist/__tests__/fixtures/example-b-decorators.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-b-extended.d.ts +0 -5
- package/dist/__tests__/fixtures/example-b-extended.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-c-custom.d.ts +0 -5
- package/dist/__tests__/fixtures/example-c-custom.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-c-decorators.d.ts +0 -5
- package/dist/__tests__/fixtures/example-c-decorators.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts +0 -6
- package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-e-decorators.d.ts +0 -11
- package/dist/__tests__/fixtures/example-e-decorators.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-e-no-namespace.d.ts +0 -5
- package/dist/__tests__/fixtures/example-e-no-namespace.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts +0 -16
- package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-nested-class.d.ts +0 -45
- package/dist/__tests__/fixtures/example-nested-class.d.ts.map +0 -1
- package/dist/__tests__/interface-types.test.d.ts +0 -11
- package/dist/__tests__/interface-types.test.d.ts.map +0 -1
- package/dist/analyzer/decorator-extractor.d.ts +0 -78
- package/dist/analyzer/decorator-extractor.d.ts.map +0 -1
- package/dist/analyzer/type-converter.d.ts +0 -75
- package/dist/analyzer/type-converter.d.ts.map +0 -1
- package/dist/codegen/index.d.ts +0 -75
- package/dist/codegen/index.d.ts.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -30,224 +30,643 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
mod
|
|
31
31
|
));
|
|
32
32
|
|
|
33
|
-
// src/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"src/json-schema/schema.ts"() {
|
|
37
|
-
"use strict";
|
|
38
|
-
import_zod = require("zod");
|
|
39
|
-
jsonSchemaTypeSchema = import_zod.z.enum([
|
|
40
|
-
"string",
|
|
41
|
-
"number",
|
|
42
|
-
"integer",
|
|
43
|
-
"boolean",
|
|
44
|
-
"object",
|
|
45
|
-
"array",
|
|
46
|
-
"null"
|
|
47
|
-
]);
|
|
48
|
-
jsonSchema7Schema = import_zod.z.lazy(
|
|
49
|
-
() => import_zod.z.object({
|
|
50
|
-
$schema: import_zod.z.string().optional(),
|
|
51
|
-
$id: import_zod.z.string().optional(),
|
|
52
|
-
$ref: import_zod.z.string().optional(),
|
|
53
|
-
// Metadata
|
|
54
|
-
title: import_zod.z.string().optional(),
|
|
55
|
-
description: import_zod.z.string().optional(),
|
|
56
|
-
deprecated: import_zod.z.boolean().optional(),
|
|
57
|
-
// Type
|
|
58
|
-
type: import_zod.z.union([jsonSchemaTypeSchema, import_zod.z.array(jsonSchemaTypeSchema)]).optional(),
|
|
59
|
-
// String validation
|
|
60
|
-
minLength: import_zod.z.number().optional(),
|
|
61
|
-
maxLength: import_zod.z.number().optional(),
|
|
62
|
-
pattern: import_zod.z.string().optional(),
|
|
63
|
-
// Number validation
|
|
64
|
-
minimum: import_zod.z.number().optional(),
|
|
65
|
-
maximum: import_zod.z.number().optional(),
|
|
66
|
-
exclusiveMinimum: import_zod.z.number().optional(),
|
|
67
|
-
exclusiveMaximum: import_zod.z.number().optional(),
|
|
68
|
-
// Enum
|
|
69
|
-
enum: import_zod.z.array(import_zod.z.union([import_zod.z.string(), import_zod.z.number(), import_zod.z.boolean(), import_zod.z.null()])).readonly().optional(),
|
|
70
|
-
const: import_zod.z.union([import_zod.z.string(), import_zod.z.number(), import_zod.z.boolean(), import_zod.z.null()]).optional(),
|
|
71
|
-
// Object
|
|
72
|
-
properties: import_zod.z.record(import_zod.z.string(), jsonSchema7Schema).optional(),
|
|
73
|
-
required: import_zod.z.array(import_zod.z.string()).optional(),
|
|
74
|
-
additionalProperties: import_zod.z.union([import_zod.z.boolean(), jsonSchema7Schema]).optional(),
|
|
75
|
-
// Array
|
|
76
|
-
items: import_zod.z.union([jsonSchema7Schema, import_zod.z.array(jsonSchema7Schema)]).optional(),
|
|
77
|
-
minItems: import_zod.z.number().optional(),
|
|
78
|
-
maxItems: import_zod.z.number().optional(),
|
|
79
|
-
// Composition
|
|
80
|
-
allOf: import_zod.z.array(jsonSchema7Schema).optional(),
|
|
81
|
-
anyOf: import_zod.z.array(jsonSchema7Schema).optional(),
|
|
82
|
-
oneOf: import_zod.z.array(jsonSchema7Schema).optional(),
|
|
83
|
-
not: jsonSchema7Schema.optional(),
|
|
84
|
-
// Conditional
|
|
85
|
-
if: jsonSchema7Schema.optional(),
|
|
86
|
-
then: jsonSchema7Schema.optional(),
|
|
87
|
-
else: jsonSchema7Schema.optional(),
|
|
88
|
-
// Format
|
|
89
|
-
format: import_zod.z.string().optional(),
|
|
90
|
-
// Default
|
|
91
|
-
default: import_zod.z.unknown().optional(),
|
|
92
|
-
// FormSpec extensions
|
|
93
|
-
"x-formspec-source": import_zod.z.string().optional(),
|
|
94
|
-
"x-formspec-params": import_zod.z.array(import_zod.z.string()).readonly().optional(),
|
|
95
|
-
"x-formspec-schemaSource": import_zod.z.string().optional()
|
|
96
|
-
}).passthrough()
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// src/json-schema/generator.ts
|
|
102
|
-
function parseOrThrow(schema, value, label) {
|
|
103
|
-
try {
|
|
104
|
-
return schema.parse(value);
|
|
105
|
-
} catch (error) {
|
|
106
|
-
if (error instanceof import_zod2.z.ZodError) {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`Generated ${label} failed validation:
|
|
109
|
-
${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
throw error;
|
|
113
|
-
}
|
|
33
|
+
// src/canonicalize/chain-dsl-canonicalizer.ts
|
|
34
|
+
function isGroup(el) {
|
|
35
|
+
return el._type === "group";
|
|
114
36
|
}
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
37
|
+
function isConditional(el) {
|
|
38
|
+
return el._type === "conditional";
|
|
39
|
+
}
|
|
40
|
+
function isField(el) {
|
|
41
|
+
return el._type === "field";
|
|
42
|
+
}
|
|
43
|
+
function canonicalizeChainDSL(form) {
|
|
120
44
|
return {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
45
|
+
kind: "form-ir",
|
|
46
|
+
irVersion: import_core.IR_VERSION,
|
|
47
|
+
elements: canonicalizeElements(form.elements),
|
|
48
|
+
typeRegistry: {},
|
|
49
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
124
50
|
};
|
|
125
51
|
}
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
52
|
+
function canonicalizeElements(elements) {
|
|
53
|
+
return elements.map(canonicalizeElement);
|
|
54
|
+
}
|
|
55
|
+
function canonicalizeElement(element) {
|
|
56
|
+
if (isField(element)) {
|
|
57
|
+
return canonicalizeField(element);
|
|
130
58
|
}
|
|
59
|
+
if (isGroup(element)) {
|
|
60
|
+
return canonicalizeGroup(element);
|
|
61
|
+
}
|
|
62
|
+
if (isConditional(element)) {
|
|
63
|
+
return canonicalizeConditional(element);
|
|
64
|
+
}
|
|
65
|
+
const _exhaustive = element;
|
|
66
|
+
throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
|
|
67
|
+
}
|
|
68
|
+
function canonicalizeField(field) {
|
|
131
69
|
switch (field._field) {
|
|
132
70
|
case "text":
|
|
133
|
-
return
|
|
71
|
+
return canonicalizeTextField(field);
|
|
134
72
|
case "number":
|
|
135
|
-
return
|
|
136
|
-
...base,
|
|
137
|
-
type: "number",
|
|
138
|
-
...field.min !== void 0 && { minimum: field.min },
|
|
139
|
-
...field.max !== void 0 && { maximum: field.max }
|
|
140
|
-
};
|
|
73
|
+
return canonicalizeNumberField(field);
|
|
141
74
|
case "boolean":
|
|
142
|
-
return
|
|
143
|
-
case "enum":
|
|
144
|
-
|
|
145
|
-
const isObjectOptions = opts.length > 0 && opts.every(
|
|
146
|
-
(opt) => typeof opt === "object" && "id" in opt && "label" in opt
|
|
147
|
-
);
|
|
148
|
-
if (isObjectOptions) {
|
|
149
|
-
return {
|
|
150
|
-
...base,
|
|
151
|
-
type: "string",
|
|
152
|
-
oneOf: opts.map((o) => ({
|
|
153
|
-
const: o.id,
|
|
154
|
-
title: o.label
|
|
155
|
-
}))
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
return { ...base, type: "string", enum: opts };
|
|
159
|
-
}
|
|
75
|
+
return canonicalizeBooleanField(field);
|
|
76
|
+
case "enum":
|
|
77
|
+
return canonicalizeStaticEnumField(field);
|
|
160
78
|
case "dynamic_enum":
|
|
161
|
-
return
|
|
162
|
-
...base,
|
|
163
|
-
type: "string",
|
|
164
|
-
"x-formspec-source": field.source,
|
|
165
|
-
...field.params !== void 0 && field.params.length > 0 && { "x-formspec-params": field.params }
|
|
166
|
-
};
|
|
79
|
+
return canonicalizeDynamicEnumField(field);
|
|
167
80
|
case "dynamic_schema":
|
|
168
|
-
return
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
};
|
|
174
|
-
case "array": {
|
|
175
|
-
const arrayField = field;
|
|
176
|
-
return {
|
|
177
|
-
...base,
|
|
178
|
-
type: "array",
|
|
179
|
-
items: generateNestedSchema(arrayField.items),
|
|
180
|
-
...arrayField.minItems !== void 0 && { minItems: arrayField.minItems },
|
|
181
|
-
...arrayField.maxItems !== void 0 && { maxItems: arrayField.maxItems }
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
case "object": {
|
|
185
|
-
const objectField = field;
|
|
186
|
-
const nestedSchema = generateNestedSchema(objectField.properties);
|
|
187
|
-
return {
|
|
188
|
-
...base,
|
|
189
|
-
...nestedSchema
|
|
190
|
-
};
|
|
191
|
-
}
|
|
81
|
+
return canonicalizeDynamicSchemaField(field);
|
|
82
|
+
case "array":
|
|
83
|
+
return canonicalizeArrayField(field);
|
|
84
|
+
case "object":
|
|
85
|
+
return canonicalizeObjectField(field);
|
|
192
86
|
default: {
|
|
193
87
|
const _exhaustive = field;
|
|
194
|
-
|
|
88
|
+
throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function canonicalizeTextField(field) {
|
|
93
|
+
const type = { kind: "primitive", primitiveKind: "string" };
|
|
94
|
+
return buildFieldNode(
|
|
95
|
+
field.name,
|
|
96
|
+
type,
|
|
97
|
+
field.required,
|
|
98
|
+
buildAnnotations(field.label, field.placeholder)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
function canonicalizeNumberField(field) {
|
|
102
|
+
const type = { kind: "primitive", primitiveKind: "number" };
|
|
103
|
+
const constraints = [];
|
|
104
|
+
if (field.min !== void 0) {
|
|
105
|
+
const c = {
|
|
106
|
+
kind: "constraint",
|
|
107
|
+
constraintKind: "minimum",
|
|
108
|
+
value: field.min,
|
|
109
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
110
|
+
};
|
|
111
|
+
constraints.push(c);
|
|
112
|
+
}
|
|
113
|
+
if (field.max !== void 0) {
|
|
114
|
+
const c = {
|
|
115
|
+
kind: "constraint",
|
|
116
|
+
constraintKind: "maximum",
|
|
117
|
+
value: field.max,
|
|
118
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
119
|
+
};
|
|
120
|
+
constraints.push(c);
|
|
121
|
+
}
|
|
122
|
+
return buildFieldNode(
|
|
123
|
+
field.name,
|
|
124
|
+
type,
|
|
125
|
+
field.required,
|
|
126
|
+
buildAnnotations(field.label),
|
|
127
|
+
constraints
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
function canonicalizeBooleanField(field) {
|
|
131
|
+
const type = { kind: "primitive", primitiveKind: "boolean" };
|
|
132
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
133
|
+
}
|
|
134
|
+
function canonicalizeStaticEnumField(field) {
|
|
135
|
+
const members = field.options.map((opt) => {
|
|
136
|
+
if (typeof opt === "string") {
|
|
137
|
+
return { value: opt };
|
|
138
|
+
}
|
|
139
|
+
return { value: opt.id, displayName: opt.label };
|
|
140
|
+
});
|
|
141
|
+
const type = { kind: "enum", members };
|
|
142
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
143
|
+
}
|
|
144
|
+
function canonicalizeDynamicEnumField(field) {
|
|
145
|
+
const type = {
|
|
146
|
+
kind: "dynamic",
|
|
147
|
+
dynamicKind: "enum",
|
|
148
|
+
sourceKey: field.source,
|
|
149
|
+
parameterFields: field.params ? [...field.params] : []
|
|
150
|
+
};
|
|
151
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
152
|
+
}
|
|
153
|
+
function canonicalizeDynamicSchemaField(field) {
|
|
154
|
+
const type = {
|
|
155
|
+
kind: "dynamic",
|
|
156
|
+
dynamicKind: "schema",
|
|
157
|
+
sourceKey: field.schemaSource,
|
|
158
|
+
parameterFields: []
|
|
159
|
+
};
|
|
160
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
161
|
+
}
|
|
162
|
+
function canonicalizeArrayField(field) {
|
|
163
|
+
const itemProperties = buildObjectProperties(field.items);
|
|
164
|
+
const itemsType = {
|
|
165
|
+
kind: "object",
|
|
166
|
+
properties: itemProperties,
|
|
167
|
+
additionalProperties: false
|
|
168
|
+
};
|
|
169
|
+
const type = { kind: "array", items: itemsType };
|
|
170
|
+
const constraints = [];
|
|
171
|
+
if (field.minItems !== void 0) {
|
|
172
|
+
const c = {
|
|
173
|
+
kind: "constraint",
|
|
174
|
+
constraintKind: "minItems",
|
|
175
|
+
value: field.minItems,
|
|
176
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
177
|
+
};
|
|
178
|
+
constraints.push(c);
|
|
179
|
+
}
|
|
180
|
+
if (field.maxItems !== void 0) {
|
|
181
|
+
const c = {
|
|
182
|
+
kind: "constraint",
|
|
183
|
+
constraintKind: "maxItems",
|
|
184
|
+
value: field.maxItems,
|
|
185
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
186
|
+
};
|
|
187
|
+
constraints.push(c);
|
|
188
|
+
}
|
|
189
|
+
return buildFieldNode(
|
|
190
|
+
field.name,
|
|
191
|
+
type,
|
|
192
|
+
field.required,
|
|
193
|
+
buildAnnotations(field.label),
|
|
194
|
+
constraints
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
function canonicalizeObjectField(field) {
|
|
198
|
+
const properties = buildObjectProperties(field.properties);
|
|
199
|
+
const type = {
|
|
200
|
+
kind: "object",
|
|
201
|
+
properties,
|
|
202
|
+
additionalProperties: false
|
|
203
|
+
};
|
|
204
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
205
|
+
}
|
|
206
|
+
function canonicalizeGroup(g) {
|
|
207
|
+
return {
|
|
208
|
+
kind: "group",
|
|
209
|
+
label: g.label,
|
|
210
|
+
elements: canonicalizeElements(g.elements),
|
|
211
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function canonicalizeConditional(c) {
|
|
215
|
+
return {
|
|
216
|
+
kind: "conditional",
|
|
217
|
+
fieldName: c.field,
|
|
218
|
+
// Conditional values from the chain DSL are JSON-serializable primitives
|
|
219
|
+
// (strings, numbers, booleans) produced by the `is()` predicate helper.
|
|
220
|
+
value: assertJsonValue(c.value),
|
|
221
|
+
elements: canonicalizeElements(c.elements),
|
|
222
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function assertJsonValue(v) {
|
|
226
|
+
if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
227
|
+
return v;
|
|
228
|
+
}
|
|
229
|
+
if (Array.isArray(v)) {
|
|
230
|
+
return v.map(assertJsonValue);
|
|
231
|
+
}
|
|
232
|
+
if (typeof v === "object") {
|
|
233
|
+
const result = {};
|
|
234
|
+
for (const [key, val] of Object.entries(v)) {
|
|
235
|
+
result[key] = assertJsonValue(val);
|
|
195
236
|
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
|
|
240
|
+
}
|
|
241
|
+
function buildFieldNode(name, type, required, annotations, constraints = []) {
|
|
242
|
+
return {
|
|
243
|
+
kind: "field",
|
|
244
|
+
name,
|
|
245
|
+
type,
|
|
246
|
+
required: required === true,
|
|
247
|
+
constraints,
|
|
248
|
+
annotations,
|
|
249
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function buildAnnotations(label, placeholder) {
|
|
253
|
+
const annotations = [];
|
|
254
|
+
if (label !== void 0) {
|
|
255
|
+
const a = {
|
|
256
|
+
kind: "annotation",
|
|
257
|
+
annotationKind: "displayName",
|
|
258
|
+
value: label,
|
|
259
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
260
|
+
};
|
|
261
|
+
annotations.push(a);
|
|
262
|
+
}
|
|
263
|
+
if (placeholder !== void 0) {
|
|
264
|
+
const a = {
|
|
265
|
+
kind: "annotation",
|
|
266
|
+
annotationKind: "placeholder",
|
|
267
|
+
value: placeholder,
|
|
268
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
269
|
+
};
|
|
270
|
+
annotations.push(a);
|
|
271
|
+
}
|
|
272
|
+
return annotations;
|
|
273
|
+
}
|
|
274
|
+
function buildObjectProperties(elements, insideConditional = false) {
|
|
275
|
+
const properties = [];
|
|
276
|
+
for (const el of elements) {
|
|
277
|
+
if (isField(el)) {
|
|
278
|
+
const fieldNode = canonicalizeField(el);
|
|
279
|
+
properties.push({
|
|
280
|
+
name: fieldNode.name,
|
|
281
|
+
type: fieldNode.type,
|
|
282
|
+
// Fields inside a conditional branch are always optional in the
|
|
283
|
+
// data schema, regardless of their `required` flag — the condition
|
|
284
|
+
// may not be met, so the field may be absent.
|
|
285
|
+
optional: insideConditional || !fieldNode.required,
|
|
286
|
+
constraints: fieldNode.constraints,
|
|
287
|
+
annotations: fieldNode.annotations,
|
|
288
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
289
|
+
});
|
|
290
|
+
} else if (isGroup(el)) {
|
|
291
|
+
properties.push(...buildObjectProperties(el.elements, insideConditional));
|
|
292
|
+
} else if (isConditional(el)) {
|
|
293
|
+
properties.push(...buildObjectProperties(el.elements, true));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return properties;
|
|
297
|
+
}
|
|
298
|
+
var import_core, CHAIN_DSL_PROVENANCE;
|
|
299
|
+
var init_chain_dsl_canonicalizer = __esm({
|
|
300
|
+
"src/canonicalize/chain-dsl-canonicalizer.ts"() {
|
|
301
|
+
"use strict";
|
|
302
|
+
import_core = require("@formspec/core");
|
|
303
|
+
CHAIN_DSL_PROVENANCE = {
|
|
304
|
+
surface: "chain-dsl",
|
|
305
|
+
file: "",
|
|
306
|
+
line: 0,
|
|
307
|
+
column: 0
|
|
308
|
+
};
|
|
196
309
|
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// src/canonicalize/tsdoc-canonicalizer.ts
|
|
313
|
+
function canonicalizeTSDoc(analysis, source) {
|
|
314
|
+
const file = source?.file ?? "";
|
|
315
|
+
const provenance = {
|
|
316
|
+
surface: "tsdoc",
|
|
317
|
+
file,
|
|
318
|
+
line: 1,
|
|
319
|
+
column: 0
|
|
320
|
+
};
|
|
321
|
+
const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
|
|
322
|
+
return {
|
|
323
|
+
kind: "form-ir",
|
|
324
|
+
irVersion: import_core2.IR_VERSION,
|
|
325
|
+
elements,
|
|
326
|
+
typeRegistry: analysis.typeRegistry,
|
|
327
|
+
provenance
|
|
328
|
+
};
|
|
197
329
|
}
|
|
198
|
-
function
|
|
330
|
+
function assembleElements(fields, layouts, provenance) {
|
|
331
|
+
const elements = [];
|
|
332
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
333
|
+
const topLevelOrder = [];
|
|
334
|
+
for (let i = 0; i < fields.length; i++) {
|
|
335
|
+
const field = fields[i];
|
|
336
|
+
const layout = layouts[i];
|
|
337
|
+
if (!field || !layout) continue;
|
|
338
|
+
const element = wrapInConditional(field, layout, provenance);
|
|
339
|
+
if (layout.groupLabel !== void 0) {
|
|
340
|
+
const label = layout.groupLabel;
|
|
341
|
+
let groupElements = groupMap.get(label);
|
|
342
|
+
if (!groupElements) {
|
|
343
|
+
groupElements = [];
|
|
344
|
+
groupMap.set(label, groupElements);
|
|
345
|
+
topLevelOrder.push({ type: "group", label });
|
|
346
|
+
}
|
|
347
|
+
groupElements.push(element);
|
|
348
|
+
} else {
|
|
349
|
+
topLevelOrder.push({ type: "element", element });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const entry of topLevelOrder) {
|
|
353
|
+
if (entry.type === "group") {
|
|
354
|
+
const groupElements = groupMap.get(entry.label);
|
|
355
|
+
if (groupElements) {
|
|
356
|
+
const groupNode = {
|
|
357
|
+
kind: "group",
|
|
358
|
+
label: entry.label,
|
|
359
|
+
elements: groupElements,
|
|
360
|
+
provenance
|
|
361
|
+
};
|
|
362
|
+
elements.push(groupNode);
|
|
363
|
+
groupMap.delete(entry.label);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
elements.push(entry.element);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return elements;
|
|
370
|
+
}
|
|
371
|
+
function wrapInConditional(field, layout, provenance) {
|
|
372
|
+
if (layout.showWhen === void 0) {
|
|
373
|
+
return field;
|
|
374
|
+
}
|
|
375
|
+
const conditional = {
|
|
376
|
+
kind: "conditional",
|
|
377
|
+
fieldName: layout.showWhen.field,
|
|
378
|
+
value: layout.showWhen.value,
|
|
379
|
+
elements: [field],
|
|
380
|
+
provenance
|
|
381
|
+
};
|
|
382
|
+
return conditional;
|
|
383
|
+
}
|
|
384
|
+
var import_core2;
|
|
385
|
+
var init_tsdoc_canonicalizer = __esm({
|
|
386
|
+
"src/canonicalize/tsdoc-canonicalizer.ts"() {
|
|
387
|
+
"use strict";
|
|
388
|
+
import_core2 = require("@formspec/core");
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// src/canonicalize/index.ts
|
|
393
|
+
var init_canonicalize = __esm({
|
|
394
|
+
"src/canonicalize/index.ts"() {
|
|
395
|
+
"use strict";
|
|
396
|
+
init_chain_dsl_canonicalizer();
|
|
397
|
+
init_tsdoc_canonicalizer();
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// src/json-schema/ir-generator.ts
|
|
402
|
+
function makeContext() {
|
|
403
|
+
return { defs: {} };
|
|
404
|
+
}
|
|
405
|
+
function generateJsonSchemaFromIR(ir) {
|
|
406
|
+
const ctx = makeContext();
|
|
407
|
+
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
408
|
+
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
409
|
+
}
|
|
410
|
+
const properties = {};
|
|
411
|
+
const required = [];
|
|
412
|
+
collectFields(ir.elements, properties, required, ctx);
|
|
413
|
+
const uniqueRequired = [...new Set(required)];
|
|
414
|
+
const result = {
|
|
415
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
416
|
+
type: "object",
|
|
417
|
+
properties,
|
|
418
|
+
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
419
|
+
};
|
|
420
|
+
if (Object.keys(ctx.defs).length > 0) {
|
|
421
|
+
result.$defs = ctx.defs;
|
|
422
|
+
}
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
function collectFields(elements, properties, required, ctx) {
|
|
199
426
|
for (const element of elements) {
|
|
200
|
-
switch (element.
|
|
427
|
+
switch (element.kind) {
|
|
201
428
|
case "field":
|
|
202
|
-
properties[element.name] =
|
|
203
|
-
if (element.required
|
|
429
|
+
properties[element.name] = generateFieldSchema(element, ctx);
|
|
430
|
+
if (element.required) {
|
|
204
431
|
required.push(element.name);
|
|
205
432
|
}
|
|
206
433
|
break;
|
|
207
434
|
case "group":
|
|
208
|
-
collectFields(element.elements, properties, required);
|
|
435
|
+
collectFields(element.elements, properties, required, ctx);
|
|
209
436
|
break;
|
|
210
437
|
case "conditional":
|
|
211
|
-
collectFields(
|
|
212
|
-
element.elements,
|
|
213
|
-
properties,
|
|
214
|
-
required
|
|
215
|
-
);
|
|
438
|
+
collectFields(element.elements, properties, required, ctx);
|
|
216
439
|
break;
|
|
440
|
+
default: {
|
|
441
|
+
const _exhaustive = element;
|
|
442
|
+
void _exhaustive;
|
|
443
|
+
}
|
|
217
444
|
}
|
|
218
445
|
}
|
|
219
446
|
}
|
|
220
|
-
function
|
|
447
|
+
function generateFieldSchema(field, ctx) {
|
|
448
|
+
const schema = generateTypeNode(field.type, ctx);
|
|
449
|
+
applyConstraints(schema, field.constraints);
|
|
450
|
+
applyAnnotations(schema, field.annotations);
|
|
451
|
+
return schema;
|
|
452
|
+
}
|
|
453
|
+
function generateTypeNode(type, ctx) {
|
|
454
|
+
switch (type.kind) {
|
|
455
|
+
case "primitive":
|
|
456
|
+
return generatePrimitiveType(type);
|
|
457
|
+
case "enum":
|
|
458
|
+
return generateEnumType(type);
|
|
459
|
+
case "array":
|
|
460
|
+
return generateArrayType(type, ctx);
|
|
461
|
+
case "object":
|
|
462
|
+
return generateObjectType(type, ctx);
|
|
463
|
+
case "union":
|
|
464
|
+
return generateUnionType(type, ctx);
|
|
465
|
+
case "reference":
|
|
466
|
+
return generateReferenceType(type);
|
|
467
|
+
case "dynamic":
|
|
468
|
+
return generateDynamicType(type);
|
|
469
|
+
case "custom":
|
|
470
|
+
return generateCustomType(type);
|
|
471
|
+
default: {
|
|
472
|
+
const _exhaustive = type;
|
|
473
|
+
return _exhaustive;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function generatePrimitiveType(type) {
|
|
478
|
+
return { type: type.primitiveKind };
|
|
479
|
+
}
|
|
480
|
+
function generateEnumType(type) {
|
|
481
|
+
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
482
|
+
if (hasDisplayNames) {
|
|
483
|
+
return {
|
|
484
|
+
oneOf: type.members.map((m) => {
|
|
485
|
+
const entry = { const: m.value };
|
|
486
|
+
if (m.displayName !== void 0) {
|
|
487
|
+
entry.title = m.displayName;
|
|
488
|
+
}
|
|
489
|
+
return entry;
|
|
490
|
+
})
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
return { enum: type.members.map((m) => m.value) };
|
|
494
|
+
}
|
|
495
|
+
function generateArrayType(type, ctx) {
|
|
496
|
+
return {
|
|
497
|
+
type: "array",
|
|
498
|
+
items: generateTypeNode(type.items, ctx)
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function generateObjectType(type, ctx) {
|
|
221
502
|
const properties = {};
|
|
222
503
|
const required = [];
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
504
|
+
for (const prop of type.properties) {
|
|
505
|
+
properties[prop.name] = generatePropertySchema(prop, ctx);
|
|
506
|
+
if (!prop.optional) {
|
|
507
|
+
required.push(prop.name);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const schema = { type: "object", properties };
|
|
511
|
+
if (required.length > 0) {
|
|
512
|
+
schema.required = required;
|
|
513
|
+
}
|
|
514
|
+
if (!type.additionalProperties) {
|
|
515
|
+
schema.additionalProperties = false;
|
|
516
|
+
}
|
|
517
|
+
return schema;
|
|
518
|
+
}
|
|
519
|
+
function generatePropertySchema(prop, ctx) {
|
|
520
|
+
const schema = generateTypeNode(prop.type, ctx);
|
|
521
|
+
applyConstraints(schema, prop.constraints);
|
|
522
|
+
applyAnnotations(schema, prop.annotations);
|
|
523
|
+
return schema;
|
|
524
|
+
}
|
|
525
|
+
function generateUnionType(type, ctx) {
|
|
526
|
+
if (isBooleanUnion(type)) {
|
|
527
|
+
return { type: "boolean" };
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function isBooleanUnion(type) {
|
|
534
|
+
if (type.members.length !== 2) return false;
|
|
535
|
+
const kinds = type.members.map((m) => m.kind);
|
|
536
|
+
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
537
|
+
}
|
|
538
|
+
function generateReferenceType(type) {
|
|
539
|
+
return { $ref: `#/$defs/${type.name}` };
|
|
540
|
+
}
|
|
541
|
+
function generateDynamicType(type) {
|
|
542
|
+
if (type.dynamicKind === "enum") {
|
|
543
|
+
const schema = {
|
|
544
|
+
type: "string",
|
|
545
|
+
"x-formspec-source": type.sourceKey
|
|
546
|
+
};
|
|
547
|
+
if (type.parameterFields.length > 0) {
|
|
548
|
+
schema["x-formspec-params"] = [...type.parameterFields];
|
|
549
|
+
}
|
|
550
|
+
return schema;
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
227
553
|
type: "object",
|
|
228
|
-
|
|
229
|
-
|
|
554
|
+
additionalProperties: true,
|
|
555
|
+
"x-formspec-schemaSource": type.sourceKey
|
|
230
556
|
};
|
|
231
|
-
return parseOrThrow(jsonSchema7Schema, result, "JSON Schema");
|
|
232
557
|
}
|
|
233
|
-
|
|
558
|
+
function generateCustomType(_type) {
|
|
559
|
+
return { type: "object" };
|
|
560
|
+
}
|
|
561
|
+
function applyConstraints(schema, constraints) {
|
|
562
|
+
for (const constraint of constraints) {
|
|
563
|
+
switch (constraint.constraintKind) {
|
|
564
|
+
case "minimum":
|
|
565
|
+
schema.minimum = constraint.value;
|
|
566
|
+
break;
|
|
567
|
+
case "maximum":
|
|
568
|
+
schema.maximum = constraint.value;
|
|
569
|
+
break;
|
|
570
|
+
case "exclusiveMinimum":
|
|
571
|
+
schema.exclusiveMinimum = constraint.value;
|
|
572
|
+
break;
|
|
573
|
+
case "exclusiveMaximum":
|
|
574
|
+
schema.exclusiveMaximum = constraint.value;
|
|
575
|
+
break;
|
|
576
|
+
case "multipleOf": {
|
|
577
|
+
const { value } = constraint;
|
|
578
|
+
if (value === 1 && schema.type === "number") {
|
|
579
|
+
schema.type = "integer";
|
|
580
|
+
} else {
|
|
581
|
+
schema.multipleOf = value;
|
|
582
|
+
}
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
case "minLength":
|
|
586
|
+
schema.minLength = constraint.value;
|
|
587
|
+
break;
|
|
588
|
+
case "maxLength":
|
|
589
|
+
schema.maxLength = constraint.value;
|
|
590
|
+
break;
|
|
591
|
+
case "minItems":
|
|
592
|
+
schema.minItems = constraint.value;
|
|
593
|
+
break;
|
|
594
|
+
case "maxItems":
|
|
595
|
+
schema.maxItems = constraint.value;
|
|
596
|
+
break;
|
|
597
|
+
case "pattern":
|
|
598
|
+
schema.pattern = constraint.pattern;
|
|
599
|
+
break;
|
|
600
|
+
case "uniqueItems":
|
|
601
|
+
schema.uniqueItems = constraint.value;
|
|
602
|
+
break;
|
|
603
|
+
case "allowedMembers":
|
|
604
|
+
break;
|
|
605
|
+
case "custom":
|
|
606
|
+
break;
|
|
607
|
+
default: {
|
|
608
|
+
const _exhaustive = constraint;
|
|
609
|
+
void _exhaustive;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function applyAnnotations(schema, annotations) {
|
|
615
|
+
for (const annotation of annotations) {
|
|
616
|
+
switch (annotation.annotationKind) {
|
|
617
|
+
case "displayName":
|
|
618
|
+
schema.title = annotation.value;
|
|
619
|
+
break;
|
|
620
|
+
case "description":
|
|
621
|
+
schema.description = annotation.value;
|
|
622
|
+
break;
|
|
623
|
+
case "defaultValue":
|
|
624
|
+
schema.default = annotation.value;
|
|
625
|
+
break;
|
|
626
|
+
case "deprecated":
|
|
627
|
+
schema.deprecated = true;
|
|
628
|
+
break;
|
|
629
|
+
case "placeholder":
|
|
630
|
+
break;
|
|
631
|
+
case "formatHint":
|
|
632
|
+
break;
|
|
633
|
+
case "custom":
|
|
634
|
+
break;
|
|
635
|
+
default: {
|
|
636
|
+
const _exhaustive = annotation;
|
|
637
|
+
void _exhaustive;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
var init_ir_generator = __esm({
|
|
643
|
+
"src/json-schema/ir-generator.ts"() {
|
|
644
|
+
"use strict";
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// src/json-schema/generator.ts
|
|
649
|
+
function generateJsonSchema(form) {
|
|
650
|
+
const ir = canonicalizeChainDSL(form);
|
|
651
|
+
return generateJsonSchemaFromIR(ir);
|
|
652
|
+
}
|
|
234
653
|
var init_generator = __esm({
|
|
235
654
|
"src/json-schema/generator.ts"() {
|
|
236
655
|
"use strict";
|
|
237
|
-
|
|
238
|
-
|
|
656
|
+
init_canonicalize();
|
|
657
|
+
init_ir_generator();
|
|
239
658
|
}
|
|
240
659
|
});
|
|
241
660
|
|
|
242
661
|
// src/ui-schema/schema.ts
|
|
243
|
-
var
|
|
244
|
-
var
|
|
662
|
+
var import_zod, jsonPointerSchema, ruleEffectSchema, uiSchemaElementTypeSchema, ruleConditionSchema, schemaBasedConditionSchema, ruleSchema, uiSchemaElementSchema, controlSchema, verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorySchema, categorizationSchema, labelElementSchema, uiSchema;
|
|
663
|
+
var init_schema = __esm({
|
|
245
664
|
"src/ui-schema/schema.ts"() {
|
|
246
665
|
"use strict";
|
|
247
|
-
|
|
248
|
-
jsonPointerSchema =
|
|
249
|
-
ruleEffectSchema =
|
|
250
|
-
uiSchemaElementTypeSchema =
|
|
666
|
+
import_zod = require("zod");
|
|
667
|
+
jsonPointerSchema = import_zod.z.string();
|
|
668
|
+
ruleEffectSchema = import_zod.z.enum(["SHOW", "HIDE", "ENABLE", "DISABLE"]);
|
|
669
|
+
uiSchemaElementTypeSchema = import_zod.z.enum([
|
|
251
670
|
"Control",
|
|
252
671
|
"VerticalLayout",
|
|
253
672
|
"HorizontalLayout",
|
|
@@ -256,32 +675,32 @@ var init_schema2 = __esm({
|
|
|
256
675
|
"Category",
|
|
257
676
|
"Label"
|
|
258
677
|
]);
|
|
259
|
-
ruleConditionSchema =
|
|
260
|
-
() =>
|
|
261
|
-
const:
|
|
262
|
-
enum:
|
|
263
|
-
type:
|
|
678
|
+
ruleConditionSchema = import_zod.z.lazy(
|
|
679
|
+
() => import_zod.z.object({
|
|
680
|
+
const: import_zod.z.unknown().optional(),
|
|
681
|
+
enum: import_zod.z.array(import_zod.z.unknown()).readonly().optional(),
|
|
682
|
+
type: import_zod.z.string().optional(),
|
|
264
683
|
not: ruleConditionSchema.optional(),
|
|
265
|
-
minimum:
|
|
266
|
-
maximum:
|
|
267
|
-
exclusiveMinimum:
|
|
268
|
-
exclusiveMaximum:
|
|
269
|
-
minLength:
|
|
270
|
-
properties:
|
|
271
|
-
required:
|
|
272
|
-
allOf:
|
|
684
|
+
minimum: import_zod.z.number().optional(),
|
|
685
|
+
maximum: import_zod.z.number().optional(),
|
|
686
|
+
exclusiveMinimum: import_zod.z.number().optional(),
|
|
687
|
+
exclusiveMaximum: import_zod.z.number().optional(),
|
|
688
|
+
minLength: import_zod.z.number().optional(),
|
|
689
|
+
properties: import_zod.z.record(import_zod.z.string(), ruleConditionSchema).optional(),
|
|
690
|
+
required: import_zod.z.array(import_zod.z.string()).optional(),
|
|
691
|
+
allOf: import_zod.z.array(ruleConditionSchema).optional()
|
|
273
692
|
}).strict()
|
|
274
693
|
);
|
|
275
|
-
schemaBasedConditionSchema =
|
|
694
|
+
schemaBasedConditionSchema = import_zod.z.object({
|
|
276
695
|
scope: jsonPointerSchema,
|
|
277
696
|
schema: ruleConditionSchema
|
|
278
697
|
}).strict();
|
|
279
|
-
ruleSchema =
|
|
698
|
+
ruleSchema = import_zod.z.object({
|
|
280
699
|
effect: ruleEffectSchema,
|
|
281
700
|
condition: schemaBasedConditionSchema
|
|
282
701
|
}).strict();
|
|
283
|
-
uiSchemaElementSchema =
|
|
284
|
-
() =>
|
|
702
|
+
uiSchemaElementSchema = import_zod.z.lazy(
|
|
703
|
+
() => import_zod.z.union([
|
|
285
704
|
controlSchema,
|
|
286
705
|
verticalLayoutSchema,
|
|
287
706
|
horizontalLayoutSchema,
|
|
@@ -291,74 +710,74 @@ var init_schema2 = __esm({
|
|
|
291
710
|
labelElementSchema
|
|
292
711
|
])
|
|
293
712
|
);
|
|
294
|
-
controlSchema =
|
|
295
|
-
type:
|
|
713
|
+
controlSchema = import_zod.z.object({
|
|
714
|
+
type: import_zod.z.literal("Control"),
|
|
296
715
|
scope: jsonPointerSchema,
|
|
297
|
-
label:
|
|
716
|
+
label: import_zod.z.union([import_zod.z.string(), import_zod.z.literal(false)]).optional(),
|
|
298
717
|
rule: ruleSchema.optional(),
|
|
299
|
-
options:
|
|
718
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
300
719
|
}).passthrough();
|
|
301
|
-
verticalLayoutSchema =
|
|
302
|
-
() =>
|
|
303
|
-
type:
|
|
304
|
-
elements:
|
|
720
|
+
verticalLayoutSchema = import_zod.z.lazy(
|
|
721
|
+
() => import_zod.z.object({
|
|
722
|
+
type: import_zod.z.literal("VerticalLayout"),
|
|
723
|
+
elements: import_zod.z.array(uiSchemaElementSchema),
|
|
305
724
|
rule: ruleSchema.optional(),
|
|
306
|
-
options:
|
|
725
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
307
726
|
}).passthrough()
|
|
308
727
|
);
|
|
309
|
-
horizontalLayoutSchema =
|
|
310
|
-
() =>
|
|
311
|
-
type:
|
|
312
|
-
elements:
|
|
728
|
+
horizontalLayoutSchema = import_zod.z.lazy(
|
|
729
|
+
() => import_zod.z.object({
|
|
730
|
+
type: import_zod.z.literal("HorizontalLayout"),
|
|
731
|
+
elements: import_zod.z.array(uiSchemaElementSchema),
|
|
313
732
|
rule: ruleSchema.optional(),
|
|
314
|
-
options:
|
|
733
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
315
734
|
}).passthrough()
|
|
316
735
|
);
|
|
317
|
-
groupLayoutSchema =
|
|
318
|
-
() =>
|
|
319
|
-
type:
|
|
320
|
-
label:
|
|
321
|
-
elements:
|
|
736
|
+
groupLayoutSchema = import_zod.z.lazy(
|
|
737
|
+
() => import_zod.z.object({
|
|
738
|
+
type: import_zod.z.literal("Group"),
|
|
739
|
+
label: import_zod.z.string(),
|
|
740
|
+
elements: import_zod.z.array(uiSchemaElementSchema),
|
|
322
741
|
rule: ruleSchema.optional(),
|
|
323
|
-
options:
|
|
742
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
324
743
|
}).passthrough()
|
|
325
744
|
);
|
|
326
|
-
categorySchema =
|
|
327
|
-
() =>
|
|
328
|
-
type:
|
|
329
|
-
label:
|
|
330
|
-
elements:
|
|
745
|
+
categorySchema = import_zod.z.lazy(
|
|
746
|
+
() => import_zod.z.object({
|
|
747
|
+
type: import_zod.z.literal("Category"),
|
|
748
|
+
label: import_zod.z.string(),
|
|
749
|
+
elements: import_zod.z.array(uiSchemaElementSchema),
|
|
331
750
|
rule: ruleSchema.optional(),
|
|
332
|
-
options:
|
|
751
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
333
752
|
}).passthrough()
|
|
334
753
|
);
|
|
335
|
-
categorizationSchema =
|
|
336
|
-
() =>
|
|
337
|
-
type:
|
|
338
|
-
elements:
|
|
339
|
-
label:
|
|
754
|
+
categorizationSchema = import_zod.z.lazy(
|
|
755
|
+
() => import_zod.z.object({
|
|
756
|
+
type: import_zod.z.literal("Categorization"),
|
|
757
|
+
elements: import_zod.z.array(categorySchema),
|
|
758
|
+
label: import_zod.z.string().optional(),
|
|
340
759
|
rule: ruleSchema.optional(),
|
|
341
|
-
options:
|
|
760
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
342
761
|
}).passthrough()
|
|
343
762
|
);
|
|
344
|
-
labelElementSchema =
|
|
345
|
-
type:
|
|
346
|
-
text:
|
|
763
|
+
labelElementSchema = import_zod.z.object({
|
|
764
|
+
type: import_zod.z.literal("Label"),
|
|
765
|
+
text: import_zod.z.string(),
|
|
347
766
|
rule: ruleSchema.optional(),
|
|
348
|
-
options:
|
|
767
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
349
768
|
}).passthrough();
|
|
350
|
-
uiSchema =
|
|
351
|
-
() =>
|
|
769
|
+
uiSchema = import_zod.z.lazy(
|
|
770
|
+
() => import_zod.z.union([verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema])
|
|
352
771
|
);
|
|
353
772
|
}
|
|
354
773
|
});
|
|
355
774
|
|
|
356
|
-
// src/ui-schema/generator.ts
|
|
357
|
-
function
|
|
775
|
+
// src/ui-schema/ir-generator.ts
|
|
776
|
+
function parseOrThrow(schema, value, label) {
|
|
358
777
|
try {
|
|
359
778
|
return schema.parse(value);
|
|
360
779
|
} catch (error) {
|
|
361
|
-
if (error instanceof
|
|
780
|
+
if (error instanceof import_zod2.z.ZodError) {
|
|
362
781
|
throw new Error(
|
|
363
782
|
`Generated ${label} failed validation:
|
|
364
783
|
${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
|
|
@@ -403,117 +822,78 @@ function combineRules(parentRule, childRule) {
|
|
|
403
822
|
}
|
|
404
823
|
};
|
|
405
824
|
}
|
|
406
|
-
function
|
|
825
|
+
function fieldNodeToControl(field, parentRule) {
|
|
826
|
+
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
827
|
+
const control = {
|
|
828
|
+
type: "Control",
|
|
829
|
+
scope: fieldToScope(field.name),
|
|
830
|
+
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
831
|
+
...parentRule !== void 0 && { rule: parentRule }
|
|
832
|
+
};
|
|
833
|
+
return control;
|
|
834
|
+
}
|
|
835
|
+
function groupNodeToLayout(group, parentRule) {
|
|
836
|
+
return {
|
|
837
|
+
type: "Group",
|
|
838
|
+
label: group.label,
|
|
839
|
+
elements: irElementsToUiSchema(group.elements, parentRule),
|
|
840
|
+
...parentRule !== void 0 && { rule: parentRule }
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function irElementsToUiSchema(elements, parentRule) {
|
|
407
844
|
const result = [];
|
|
408
845
|
for (const element of elements) {
|
|
409
|
-
switch (element.
|
|
846
|
+
switch (element.kind) {
|
|
410
847
|
case "field": {
|
|
411
|
-
|
|
412
|
-
type: "Control",
|
|
413
|
-
scope: fieldToScope(element.name),
|
|
414
|
-
...element.label !== void 0 && { label: element.label },
|
|
415
|
-
...parentRule !== void 0 && { rule: parentRule }
|
|
416
|
-
};
|
|
417
|
-
result.push(control);
|
|
848
|
+
result.push(fieldNodeToControl(element, parentRule));
|
|
418
849
|
break;
|
|
419
850
|
}
|
|
420
851
|
case "group": {
|
|
421
|
-
|
|
422
|
-
const group = {
|
|
423
|
-
type: "Group",
|
|
424
|
-
label: groupElement.label,
|
|
425
|
-
elements: elementsToUiSchema(groupElement.elements, parentRule),
|
|
426
|
-
...parentRule !== void 0 && { rule: parentRule }
|
|
427
|
-
};
|
|
428
|
-
result.push(group);
|
|
852
|
+
result.push(groupNodeToLayout(element, parentRule));
|
|
429
853
|
break;
|
|
430
854
|
}
|
|
431
855
|
case "conditional": {
|
|
432
|
-
const
|
|
433
|
-
const newRule = createShowRule(conditionalElement.field, conditionalElement.value);
|
|
856
|
+
const newRule = createShowRule(element.fieldName, element.value);
|
|
434
857
|
const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
|
|
435
|
-
const childElements =
|
|
858
|
+
const childElements = irElementsToUiSchema(element.elements, combinedRule);
|
|
436
859
|
result.push(...childElements);
|
|
437
860
|
break;
|
|
438
861
|
}
|
|
862
|
+
default: {
|
|
863
|
+
const _exhaustive = element;
|
|
864
|
+
void _exhaustive;
|
|
865
|
+
throw new Error("Unhandled IR element kind");
|
|
866
|
+
}
|
|
439
867
|
}
|
|
440
868
|
}
|
|
441
869
|
return result;
|
|
442
870
|
}
|
|
443
|
-
function
|
|
444
|
-
const control = {
|
|
445
|
-
type: "Control",
|
|
446
|
-
scope: `${scopePrefix}/${field.id}`
|
|
447
|
-
};
|
|
448
|
-
if (field.label !== void 0) {
|
|
449
|
-
control.label = field.label;
|
|
450
|
-
}
|
|
451
|
-
if (field.showWhen !== void 0 && typeof field.showWhen === "object" && "field" in field.showWhen && "value" in field.showWhen) {
|
|
452
|
-
const sw = field.showWhen;
|
|
453
|
-
control.rule = {
|
|
454
|
-
effect: "SHOW",
|
|
455
|
-
condition: {
|
|
456
|
-
scope: `#/properties/${sw.field}`,
|
|
457
|
-
schema: { const: sw.value }
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
return control;
|
|
462
|
-
}
|
|
463
|
-
function generateUiSchemaFromFields(fields) {
|
|
464
|
-
const groupMap = /* @__PURE__ */ new Map();
|
|
465
|
-
const orderedKeys = [];
|
|
466
|
-
const ungrouped = [];
|
|
467
|
-
for (const field of fields) {
|
|
468
|
-
const element = formSpecFieldToElement(field);
|
|
469
|
-
if (field.group !== void 0) {
|
|
470
|
-
if (!groupMap.has(field.group)) {
|
|
471
|
-
groupMap.set(field.group, []);
|
|
472
|
-
orderedKeys.push(field.group);
|
|
473
|
-
}
|
|
474
|
-
groupMap.get(field.group).push(element);
|
|
475
|
-
} else {
|
|
476
|
-
orderedKeys.push(null);
|
|
477
|
-
ungrouped.push(element);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
const elements = [];
|
|
481
|
-
let ungroupedIndex = 0;
|
|
482
|
-
for (const key of orderedKeys) {
|
|
483
|
-
if (key === null) {
|
|
484
|
-
const el = ungrouped[ungroupedIndex++];
|
|
485
|
-
if (el !== void 0) {
|
|
486
|
-
elements.push(el);
|
|
487
|
-
}
|
|
488
|
-
} else {
|
|
489
|
-
const groupElements = groupMap.get(key) ?? [];
|
|
490
|
-
const groupLayout = {
|
|
491
|
-
type: "Group",
|
|
492
|
-
label: key,
|
|
493
|
-
elements: groupElements
|
|
494
|
-
};
|
|
495
|
-
elements.push(groupLayout);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
871
|
+
function generateUiSchemaFromIR(ir) {
|
|
498
872
|
const result = {
|
|
499
873
|
type: "VerticalLayout",
|
|
500
|
-
elements
|
|
874
|
+
elements: irElementsToUiSchema(ir.elements)
|
|
501
875
|
};
|
|
502
|
-
return
|
|
876
|
+
return parseOrThrow(uiSchema, result, "UI Schema");
|
|
503
877
|
}
|
|
878
|
+
var import_zod2;
|
|
879
|
+
var init_ir_generator2 = __esm({
|
|
880
|
+
"src/ui-schema/ir-generator.ts"() {
|
|
881
|
+
"use strict";
|
|
882
|
+
init_schema();
|
|
883
|
+
import_zod2 = require("zod");
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
// src/ui-schema/generator.ts
|
|
504
888
|
function generateUiSchema(form) {
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
elements: elementsToUiSchema(form.elements)
|
|
508
|
-
};
|
|
509
|
-
return parseOrThrow2(uiSchema, result, "UI Schema");
|
|
889
|
+
const ir = canonicalizeChainDSL(form);
|
|
890
|
+
return generateUiSchemaFromIR(ir);
|
|
510
891
|
}
|
|
511
|
-
var import_zod4;
|
|
512
892
|
var init_generator2 = __esm({
|
|
513
893
|
"src/ui-schema/generator.ts"() {
|
|
514
894
|
"use strict";
|
|
515
|
-
|
|
516
|
-
|
|
895
|
+
init_canonicalize();
|
|
896
|
+
init_ir_generator2();
|
|
517
897
|
}
|
|
518
898
|
});
|
|
519
899
|
|
|
@@ -530,294 +910,231 @@ var init_types = __esm({
|
|
|
530
910
|
}
|
|
531
911
|
});
|
|
532
912
|
|
|
533
|
-
// src/
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
return node.elements.map((el) => {
|
|
599
|
-
if (ts.isSpreadElement(el)) {
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
|
-
return extractArgValue(el);
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
if (ts.isObjectLiteralExpression(node)) {
|
|
606
|
-
const obj = {};
|
|
607
|
-
for (const prop of node.properties) {
|
|
608
|
-
if (ts.isPropertyAssignment(prop)) {
|
|
609
|
-
const key = getPropertyName(prop.name);
|
|
610
|
-
if (key) {
|
|
611
|
-
obj[key] = extractArgValue(prop.initializer);
|
|
612
|
-
}
|
|
613
|
-
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
614
|
-
const key = prop.name.text;
|
|
615
|
-
obj[key] = null;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return obj;
|
|
619
|
-
}
|
|
620
|
-
if (ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
621
|
-
return node.text;
|
|
622
|
-
}
|
|
623
|
-
if (ts.isRegularExpressionLiteral(node)) {
|
|
624
|
-
const regexText = node.text;
|
|
625
|
-
const lastSlash = regexText.lastIndexOf("/");
|
|
626
|
-
if (lastSlash > 0) {
|
|
627
|
-
return regexText.substring(1, lastSlash);
|
|
628
|
-
}
|
|
629
|
-
return regexText;
|
|
913
|
+
// src/json-schema/schema.ts
|
|
914
|
+
var import_zod3, jsonSchemaTypeSchema, jsonSchema7Schema;
|
|
915
|
+
var init_schema2 = __esm({
|
|
916
|
+
"src/json-schema/schema.ts"() {
|
|
917
|
+
"use strict";
|
|
918
|
+
import_zod3 = require("zod");
|
|
919
|
+
jsonSchemaTypeSchema = import_zod3.z.enum([
|
|
920
|
+
"string",
|
|
921
|
+
"number",
|
|
922
|
+
"integer",
|
|
923
|
+
"boolean",
|
|
924
|
+
"object",
|
|
925
|
+
"array",
|
|
926
|
+
"null"
|
|
927
|
+
]);
|
|
928
|
+
jsonSchema7Schema = import_zod3.z.lazy(
|
|
929
|
+
() => import_zod3.z.object({
|
|
930
|
+
$schema: import_zod3.z.string().optional(),
|
|
931
|
+
$id: import_zod3.z.string().optional(),
|
|
932
|
+
$ref: import_zod3.z.string().optional(),
|
|
933
|
+
// Metadata
|
|
934
|
+
title: import_zod3.z.string().optional(),
|
|
935
|
+
description: import_zod3.z.string().optional(),
|
|
936
|
+
deprecated: import_zod3.z.boolean().optional(),
|
|
937
|
+
// Type
|
|
938
|
+
type: import_zod3.z.union([jsonSchemaTypeSchema, import_zod3.z.array(jsonSchemaTypeSchema)]).optional(),
|
|
939
|
+
// String validation
|
|
940
|
+
minLength: import_zod3.z.number().optional(),
|
|
941
|
+
maxLength: import_zod3.z.number().optional(),
|
|
942
|
+
pattern: import_zod3.z.string().optional(),
|
|
943
|
+
// Number validation
|
|
944
|
+
minimum: import_zod3.z.number().optional(),
|
|
945
|
+
maximum: import_zod3.z.number().optional(),
|
|
946
|
+
exclusiveMinimum: import_zod3.z.number().optional(),
|
|
947
|
+
exclusiveMaximum: import_zod3.z.number().optional(),
|
|
948
|
+
// Enum
|
|
949
|
+
enum: import_zod3.z.array(import_zod3.z.union([import_zod3.z.string(), import_zod3.z.number(), import_zod3.z.boolean(), import_zod3.z.null()])).readonly().optional(),
|
|
950
|
+
const: import_zod3.z.union([import_zod3.z.string(), import_zod3.z.number(), import_zod3.z.boolean(), import_zod3.z.null()]).optional(),
|
|
951
|
+
// Object
|
|
952
|
+
properties: import_zod3.z.record(import_zod3.z.string(), jsonSchema7Schema).optional(),
|
|
953
|
+
required: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
954
|
+
additionalProperties: import_zod3.z.union([import_zod3.z.boolean(), jsonSchema7Schema]).optional(),
|
|
955
|
+
// Array
|
|
956
|
+
items: import_zod3.z.union([jsonSchema7Schema, import_zod3.z.array(jsonSchema7Schema)]).optional(),
|
|
957
|
+
minItems: import_zod3.z.number().optional(),
|
|
958
|
+
maxItems: import_zod3.z.number().optional(),
|
|
959
|
+
// Composition
|
|
960
|
+
allOf: import_zod3.z.array(jsonSchema7Schema).optional(),
|
|
961
|
+
anyOf: import_zod3.z.array(jsonSchema7Schema).optional(),
|
|
962
|
+
oneOf: import_zod3.z.array(jsonSchema7Schema).optional(),
|
|
963
|
+
not: jsonSchema7Schema.optional(),
|
|
964
|
+
// Conditional
|
|
965
|
+
if: jsonSchema7Schema.optional(),
|
|
966
|
+
then: jsonSchema7Schema.optional(),
|
|
967
|
+
else: jsonSchema7Schema.optional(),
|
|
968
|
+
// Format
|
|
969
|
+
format: import_zod3.z.string().optional(),
|
|
970
|
+
// Default
|
|
971
|
+
default: import_zod3.z.unknown().optional(),
|
|
972
|
+
// FormSpec extensions
|
|
973
|
+
"x-formspec-source": import_zod3.z.string().optional(),
|
|
974
|
+
"x-formspec-params": import_zod3.z.array(import_zod3.z.string()).readonly().optional(),
|
|
975
|
+
"x-formspec-schemaSource": import_zod3.z.string().optional()
|
|
976
|
+
}).passthrough()
|
|
977
|
+
);
|
|
630
978
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// src/analyzer/program.ts
|
|
982
|
+
function createProgramContext(filePath) {
|
|
983
|
+
const absolutePath = path.resolve(filePath);
|
|
984
|
+
const fileDir = path.dirname(absolutePath);
|
|
985
|
+
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
986
|
+
let compilerOptions;
|
|
987
|
+
let fileNames;
|
|
988
|
+
if (configPath) {
|
|
989
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
990
|
+
if (configFile.error) {
|
|
991
|
+
throw new Error(
|
|
992
|
+
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
993
|
+
);
|
|
637
994
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
return name.text;
|
|
647
|
-
}
|
|
648
|
-
if (ts.isStringLiteral(name)) {
|
|
649
|
-
return name.text;
|
|
650
|
-
}
|
|
651
|
-
if (ts.isNumericLiteral(name)) {
|
|
652
|
-
return name.text;
|
|
653
|
-
}
|
|
654
|
-
return null;
|
|
655
|
-
}
|
|
656
|
-
function isFormSpecDecoratorsPath(fileName) {
|
|
657
|
-
const normalized = fileName.replace(/\\/g, "/");
|
|
658
|
-
return normalized.includes("node_modules/@formspec/decorators") || normalized.includes("/packages/decorators/");
|
|
659
|
-
}
|
|
660
|
-
function resolveDecorator(decorator, checker) {
|
|
661
|
-
const expr = decorator.expression;
|
|
662
|
-
let targetNode;
|
|
663
|
-
let name;
|
|
664
|
-
if (ts.isIdentifier(expr)) {
|
|
665
|
-
targetNode = expr;
|
|
666
|
-
name = expr.text;
|
|
667
|
-
} else if (ts.isCallExpression(expr)) {
|
|
668
|
-
if (ts.isIdentifier(expr.expression)) {
|
|
669
|
-
targetNode = expr.expression;
|
|
670
|
-
name = expr.expression.text;
|
|
671
|
-
} else {
|
|
672
|
-
return null;
|
|
995
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
996
|
+
configFile.config,
|
|
997
|
+
ts.sys,
|
|
998
|
+
path.dirname(configPath)
|
|
999
|
+
);
|
|
1000
|
+
if (parsed.errors.length > 0) {
|
|
1001
|
+
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
1002
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
673
1003
|
}
|
|
1004
|
+
compilerOptions = parsed.options;
|
|
1005
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
674
1006
|
} else {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const decl = declarations[0];
|
|
683
|
-
if (decl) {
|
|
684
|
-
const sourceFile = decl.getSourceFile();
|
|
685
|
-
const fileName = sourceFile.fileName;
|
|
686
|
-
if (isFormSpecDecoratorsPath(fileName)) {
|
|
687
|
-
return {
|
|
688
|
-
name,
|
|
689
|
-
isFormSpec: true,
|
|
690
|
-
isMarker: !ts.isCallExpression(expr)
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
const resolvedSymbol = checker.getSymbolAtLocation(targetNode);
|
|
698
|
-
if (!resolvedSymbol) return null;
|
|
699
|
-
const type = checker.getTypeOfSymbol(resolvedSymbol);
|
|
700
|
-
const props = type.getProperties();
|
|
701
|
-
let extendsBuiltin;
|
|
702
|
-
let extensionName;
|
|
703
|
-
let isMarker = false;
|
|
704
|
-
for (const prop of props) {
|
|
705
|
-
const escapedName = prop.getEscapedName();
|
|
706
|
-
if (escapedName.startsWith("__@") && (escapedName.includes("formspec.extends") || escapedName.includes("FORMSPEC_EXTENDS"))) {
|
|
707
|
-
const propType = checker.getTypeOfSymbol(prop);
|
|
708
|
-
if (propType.isStringLiteral()) {
|
|
709
|
-
extendsBuiltin = propType.value;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
if (escapedName.startsWith("__@") && (escapedName.includes("formspec.extension") || escapedName.includes("FORMSPEC_EXTENSION"))) {
|
|
713
|
-
const propType = checker.getTypeOfSymbol(prop);
|
|
714
|
-
if (propType.isStringLiteral()) {
|
|
715
|
-
extensionName = propType.value;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
if (escapedName.startsWith("__@") && (escapedName.includes("formspec.marker") || escapedName.includes("FORMSPEC_MARKER"))) {
|
|
719
|
-
isMarker = true;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
if (extendsBuiltin) {
|
|
723
|
-
return {
|
|
724
|
-
name,
|
|
725
|
-
extendsBuiltin,
|
|
726
|
-
isFormSpec: true,
|
|
727
|
-
isMarker: false
|
|
1007
|
+
compilerOptions = {
|
|
1008
|
+
target: ts.ScriptTarget.ES2022,
|
|
1009
|
+
module: ts.ModuleKind.NodeNext,
|
|
1010
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
1011
|
+
strict: true,
|
|
1012
|
+
skipLibCheck: true,
|
|
1013
|
+
declaration: true
|
|
728
1014
|
};
|
|
1015
|
+
fileNames = [absolutePath];
|
|
729
1016
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
isFormSpec: true,
|
|
735
|
-
isMarker
|
|
736
|
-
};
|
|
1017
|
+
const program = ts.createProgram(fileNames, compilerOptions);
|
|
1018
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
1019
|
+
if (!sourceFile) {
|
|
1020
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
737
1021
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1022
|
+
return {
|
|
1023
|
+
program,
|
|
1024
|
+
checker: program.getTypeChecker(),
|
|
1025
|
+
sourceFile
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
1029
|
+
let result = null;
|
|
1030
|
+
function visit(node) {
|
|
1031
|
+
if (result) return;
|
|
1032
|
+
if (predicate(node) && getName(node) === name) {
|
|
1033
|
+
result = node;
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
ts.forEachChild(node, visit);
|
|
744
1037
|
}
|
|
745
|
-
|
|
1038
|
+
visit(sourceFile);
|
|
1039
|
+
return result;
|
|
1040
|
+
}
|
|
1041
|
+
function findClassByName(sourceFile, className) {
|
|
1042
|
+
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
1043
|
+
}
|
|
1044
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
1045
|
+
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
1046
|
+
}
|
|
1047
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
1048
|
+
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
746
1049
|
}
|
|
747
|
-
var ts,
|
|
748
|
-
var
|
|
749
|
-
"src/analyzer/
|
|
1050
|
+
var ts, path;
|
|
1051
|
+
var init_program = __esm({
|
|
1052
|
+
"src/analyzer/program.ts"() {
|
|
750
1053
|
"use strict";
|
|
751
1054
|
ts = __toESM(require("typescript"), 1);
|
|
752
|
-
|
|
753
|
-
FORMSPEC_DECORATORS = {
|
|
754
|
-
// Display metadata
|
|
755
|
-
Field: { argTypes: ["object"] },
|
|
756
|
-
// Grouping
|
|
757
|
-
Group: { argTypes: ["string"] },
|
|
758
|
-
// Conditional display
|
|
759
|
-
ShowWhen: { argTypes: ["object"] },
|
|
760
|
-
// Enum options
|
|
761
|
-
EnumOptions: { argTypes: ["array"] },
|
|
762
|
-
// Numeric constraints
|
|
763
|
-
Minimum: { argTypes: ["number"] },
|
|
764
|
-
Maximum: { argTypes: ["number"] },
|
|
765
|
-
ExclusiveMinimum: { argTypes: ["number"] },
|
|
766
|
-
ExclusiveMaximum: { argTypes: ["number"] },
|
|
767
|
-
// String constraints
|
|
768
|
-
MinLength: { argTypes: ["number"] },
|
|
769
|
-
MaxLength: { argTypes: ["number"] },
|
|
770
|
-
Pattern: { argTypes: ["string"] }
|
|
771
|
-
};
|
|
1055
|
+
path = __toESM(require("path"), 1);
|
|
772
1056
|
}
|
|
773
1057
|
});
|
|
774
1058
|
|
|
775
|
-
// src/analyzer/
|
|
776
|
-
function
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1059
|
+
// src/analyzer/tsdoc-parser.ts
|
|
1060
|
+
function isBuiltinConstraintName(tagName) {
|
|
1061
|
+
return tagName in import_core3.BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
1062
|
+
}
|
|
1063
|
+
function createFormSpecTSDocConfig() {
|
|
1064
|
+
const config = new import_tsdoc.TSDocConfiguration();
|
|
1065
|
+
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
1066
|
+
config.addTagDefinition(
|
|
1067
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
1068
|
+
tagName: "@" + tagName,
|
|
1069
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
1070
|
+
allowMultiple: true
|
|
1071
|
+
})
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
return config;
|
|
1075
|
+
}
|
|
1076
|
+
function getParser() {
|
|
1077
|
+
sharedParser ??= new import_tsdoc.TSDocParser(createFormSpecTSDocConfig());
|
|
1078
|
+
return sharedParser;
|
|
1079
|
+
}
|
|
1080
|
+
function parseTSDocTags(node, file = "") {
|
|
1081
|
+
const constraints = [];
|
|
1082
|
+
const annotations = [];
|
|
1083
|
+
const sourceFile = node.getSourceFile();
|
|
1084
|
+
const sourceText = sourceFile.getFullText();
|
|
1085
|
+
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1086
|
+
if (commentRanges) {
|
|
1087
|
+
for (const range of commentRanges) {
|
|
1088
|
+
if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
|
|
797
1089
|
continue;
|
|
798
1090
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
try {
|
|
802
|
-
const parsed = JSON.parse(trimmed);
|
|
803
|
-
if (!Array.isArray(parsed)) {
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
results.push(createSyntheticDecorator(constraintName, parsed));
|
|
807
|
-
} catch {
|
|
1091
|
+
const commentText = sourceText.substring(range.pos, range.end);
|
|
1092
|
+
if (!commentText.startsWith("/**")) {
|
|
808
1093
|
continue;
|
|
809
1094
|
}
|
|
810
|
-
|
|
811
|
-
|
|
1095
|
+
const parser = getParser();
|
|
1096
|
+
const parserContext = parser.parseRange(
|
|
1097
|
+
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1098
|
+
);
|
|
1099
|
+
const docComment = parserContext.docComment;
|
|
1100
|
+
for (const block of docComment.customBlocks) {
|
|
1101
|
+
const tagName = block.blockTag.tagName.substring(1);
|
|
1102
|
+
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1103
|
+
const text = extractBlockText(block).trim();
|
|
1104
|
+
if (text === "") continue;
|
|
1105
|
+
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1106
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1107
|
+
if (constraintNode) {
|
|
1108
|
+
constraints.push(constraintNode);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (docComment.deprecatedBlock !== void 0) {
|
|
1112
|
+
annotations.push({
|
|
1113
|
+
kind: "annotation",
|
|
1114
|
+
annotationKind: "deprecated",
|
|
1115
|
+
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1121
|
+
for (const tag of jsDocTagsAll) {
|
|
1122
|
+
const tagName = tag.tagName.text;
|
|
1123
|
+
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1124
|
+
const commentText = getTagCommentText(tag);
|
|
1125
|
+
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
1126
|
+
const text = commentText.trim();
|
|
1127
|
+
const provenance = provenanceForJSDocTag(tag, file);
|
|
1128
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1129
|
+
if (constraintNode) {
|
|
1130
|
+
constraints.push(constraintNode);
|
|
812
1131
|
}
|
|
813
1132
|
}
|
|
814
|
-
return results;
|
|
815
|
-
}
|
|
816
|
-
function extractJSDocFieldMetadata(node) {
|
|
817
|
-
const jsDocTags = ts2.getJSDocTags(node);
|
|
818
1133
|
let displayName;
|
|
819
1134
|
let description;
|
|
820
|
-
|
|
1135
|
+
let displayNameTag;
|
|
1136
|
+
let descriptionTag;
|
|
1137
|
+
for (const tag of jsDocTagsAll) {
|
|
821
1138
|
const tagName = tag.tagName.text;
|
|
822
1139
|
const commentText = getTagCommentText(tag);
|
|
823
1140
|
if (commentText === void 0 || commentText.trim() === "") {
|
|
@@ -826,18 +1143,132 @@ function extractJSDocFieldMetadata(node) {
|
|
|
826
1143
|
const trimmed = commentText.trim();
|
|
827
1144
|
if (tagName === "Field_displayName") {
|
|
828
1145
|
displayName = trimmed;
|
|
1146
|
+
displayNameTag = tag;
|
|
829
1147
|
} else if (tagName === "Field_description") {
|
|
830
1148
|
description = trimmed;
|
|
1149
|
+
descriptionTag = tag;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
if (displayName !== void 0 && displayNameTag) {
|
|
1153
|
+
annotations.push({
|
|
1154
|
+
kind: "annotation",
|
|
1155
|
+
annotationKind: "displayName",
|
|
1156
|
+
value: displayName,
|
|
1157
|
+
provenance: provenanceForJSDocTag(displayNameTag, file)
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
if (description !== void 0 && descriptionTag) {
|
|
1161
|
+
annotations.push({
|
|
1162
|
+
kind: "annotation",
|
|
1163
|
+
annotationKind: "description",
|
|
1164
|
+
value: description,
|
|
1165
|
+
provenance: provenanceForJSDocTag(descriptionTag, file)
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
return { constraints, annotations };
|
|
1169
|
+
}
|
|
1170
|
+
function extractBlockText(block) {
|
|
1171
|
+
return extractPlainText(block.content);
|
|
1172
|
+
}
|
|
1173
|
+
function extractPlainText(node) {
|
|
1174
|
+
let result = "";
|
|
1175
|
+
if (node instanceof import_tsdoc.DocPlainText) {
|
|
1176
|
+
return node.text;
|
|
1177
|
+
}
|
|
1178
|
+
if (node instanceof import_tsdoc.DocSoftBreak) {
|
|
1179
|
+
return " ";
|
|
1180
|
+
}
|
|
1181
|
+
if (typeof node.getChildNodes === "function") {
|
|
1182
|
+
for (const child of node.getChildNodes()) {
|
|
1183
|
+
result += extractPlainText(child);
|
|
831
1184
|
}
|
|
832
1185
|
}
|
|
833
|
-
|
|
1186
|
+
return result;
|
|
1187
|
+
}
|
|
1188
|
+
function parseConstraintValue(tagName, text, provenance) {
|
|
1189
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1193
|
+
if (expectedType === "number") {
|
|
1194
|
+
const value = Number(text);
|
|
1195
|
+
if (Number.isNaN(value)) {
|
|
1196
|
+
return null;
|
|
1197
|
+
}
|
|
1198
|
+
const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
|
|
1199
|
+
if (numericKind) {
|
|
1200
|
+
return {
|
|
1201
|
+
kind: "constraint",
|
|
1202
|
+
constraintKind: numericKind,
|
|
1203
|
+
value,
|
|
1204
|
+
provenance
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
|
|
1208
|
+
if (lengthKind) {
|
|
1209
|
+
return {
|
|
1210
|
+
kind: "constraint",
|
|
1211
|
+
constraintKind: lengthKind,
|
|
1212
|
+
value,
|
|
1213
|
+
provenance
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
834
1216
|
return null;
|
|
835
1217
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
1218
|
+
if (expectedType === "json") {
|
|
1219
|
+
try {
|
|
1220
|
+
const parsed = JSON.parse(text);
|
|
1221
|
+
if (!Array.isArray(parsed)) {
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
const members = [];
|
|
1225
|
+
for (const item of parsed) {
|
|
1226
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
1227
|
+
members.push(item);
|
|
1228
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
1229
|
+
const id = item["id"];
|
|
1230
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
1231
|
+
members.push(id);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
kind: "constraint",
|
|
1237
|
+
constraintKind: "allowedMembers",
|
|
1238
|
+
members,
|
|
1239
|
+
provenance
|
|
1240
|
+
};
|
|
1241
|
+
} catch {
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return {
|
|
1246
|
+
kind: "constraint",
|
|
1247
|
+
constraintKind: "pattern",
|
|
1248
|
+
pattern: text,
|
|
1249
|
+
provenance
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1253
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1254
|
+
return {
|
|
1255
|
+
surface: "tsdoc",
|
|
1256
|
+
file,
|
|
1257
|
+
line: line + 1,
|
|
1258
|
+
column: character,
|
|
1259
|
+
tagName: "@" + tagName
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
function provenanceForJSDocTag(tag, file) {
|
|
1263
|
+
const sourceFile = tag.getSourceFile();
|
|
1264
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
1265
|
+
return {
|
|
1266
|
+
surface: "tsdoc",
|
|
1267
|
+
file,
|
|
1268
|
+
line: line + 1,
|
|
1269
|
+
column: character,
|
|
1270
|
+
tagName: "@" + tag.tagName.text
|
|
839
1271
|
};
|
|
840
|
-
return createSyntheticDecorator("Field", fieldOpts);
|
|
841
1272
|
}
|
|
842
1273
|
function getTagCommentText(tag) {
|
|
843
1274
|
if (tag.comment === void 0) {
|
|
@@ -848,38 +1279,105 @@ function getTagCommentText(tag) {
|
|
|
848
1279
|
}
|
|
849
1280
|
return ts2.getTextOfJSDocComment(tag.comment);
|
|
850
1281
|
}
|
|
851
|
-
|
|
1282
|
+
var ts2, import_tsdoc, import_core3, NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, sharedParser;
|
|
1283
|
+
var init_tsdoc_parser = __esm({
|
|
1284
|
+
"src/analyzer/tsdoc-parser.ts"() {
|
|
1285
|
+
"use strict";
|
|
1286
|
+
ts2 = __toESM(require("typescript"), 1);
|
|
1287
|
+
import_tsdoc = require("@microsoft/tsdoc");
|
|
1288
|
+
import_core3 = require("@formspec/core");
|
|
1289
|
+
NUMERIC_CONSTRAINT_MAP = {
|
|
1290
|
+
Minimum: "minimum",
|
|
1291
|
+
Maximum: "maximum",
|
|
1292
|
+
ExclusiveMinimum: "exclusiveMinimum",
|
|
1293
|
+
ExclusiveMaximum: "exclusiveMaximum"
|
|
1294
|
+
};
|
|
1295
|
+
LENGTH_CONSTRAINT_MAP = {
|
|
1296
|
+
MinLength: "minLength",
|
|
1297
|
+
MaxLength: "maxLength"
|
|
1298
|
+
};
|
|
1299
|
+
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
// src/analyzer/jsdoc-constraints.ts
|
|
1304
|
+
function extractJSDocConstraintNodes(node, file = "") {
|
|
1305
|
+
const result = parseTSDocTags(node, file);
|
|
1306
|
+
return [...result.constraints];
|
|
1307
|
+
}
|
|
1308
|
+
function extractJSDocAnnotationNodes(node, file = "") {
|
|
1309
|
+
const result = parseTSDocTags(node, file);
|
|
1310
|
+
return [...result.annotations];
|
|
1311
|
+
}
|
|
1312
|
+
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
1313
|
+
if (!initializer) return null;
|
|
1314
|
+
let value;
|
|
1315
|
+
if (ts3.isStringLiteral(initializer)) {
|
|
1316
|
+
value = initializer.text;
|
|
1317
|
+
} else if (ts3.isNumericLiteral(initializer)) {
|
|
1318
|
+
value = Number(initializer.text);
|
|
1319
|
+
} else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
|
|
1320
|
+
value = true;
|
|
1321
|
+
} else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
|
|
1322
|
+
value = false;
|
|
1323
|
+
} else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
|
|
1324
|
+
value = null;
|
|
1325
|
+
} else if (ts3.isPrefixUnaryExpression(initializer)) {
|
|
1326
|
+
if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
|
|
1327
|
+
value = -Number(initializer.operand.text);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (value === void 0) return null;
|
|
1331
|
+
const sourceFile = initializer.getSourceFile();
|
|
1332
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(initializer.getStart());
|
|
852
1333
|
return {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1334
|
+
kind: "annotation",
|
|
1335
|
+
annotationKind: "defaultValue",
|
|
1336
|
+
value,
|
|
1337
|
+
provenance: {
|
|
1338
|
+
surface: "tsdoc",
|
|
1339
|
+
file,
|
|
1340
|
+
line: line + 1,
|
|
1341
|
+
column: character
|
|
1342
|
+
}
|
|
856
1343
|
};
|
|
857
1344
|
}
|
|
858
|
-
var
|
|
1345
|
+
var ts3, import_core4;
|
|
859
1346
|
var init_jsdoc_constraints = __esm({
|
|
860
1347
|
"src/analyzer/jsdoc-constraints.ts"() {
|
|
861
1348
|
"use strict";
|
|
862
|
-
|
|
863
|
-
|
|
1349
|
+
ts3 = __toESM(require("typescript"), 1);
|
|
1350
|
+
import_core4 = require("@formspec/core");
|
|
1351
|
+
init_tsdoc_parser();
|
|
864
1352
|
}
|
|
865
1353
|
});
|
|
866
1354
|
|
|
867
1355
|
// src/analyzer/class-analyzer.ts
|
|
868
|
-
function
|
|
1356
|
+
function isObjectType(type) {
|
|
1357
|
+
return !!(type.flags & ts4.TypeFlags.Object);
|
|
1358
|
+
}
|
|
1359
|
+
function isTypeReference(type) {
|
|
1360
|
+
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
1361
|
+
}
|
|
1362
|
+
function analyzeClassToIR(classDecl, checker, file = "") {
|
|
869
1363
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
870
1364
|
const fields = [];
|
|
1365
|
+
const fieldLayouts = [];
|
|
1366
|
+
const typeRegistry = {};
|
|
1367
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
871
1368
|
const instanceMethods = [];
|
|
872
1369
|
const staticMethods = [];
|
|
873
1370
|
for (const member of classDecl.members) {
|
|
874
|
-
if (
|
|
875
|
-
const
|
|
876
|
-
if (
|
|
877
|
-
fields.push(
|
|
1371
|
+
if (ts4.isPropertyDeclaration(member)) {
|
|
1372
|
+
const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
|
|
1373
|
+
if (fieldNode) {
|
|
1374
|
+
fields.push(fieldNode);
|
|
1375
|
+
fieldLayouts.push({});
|
|
878
1376
|
}
|
|
879
|
-
} else if (
|
|
1377
|
+
} else if (ts4.isMethodDeclaration(member)) {
|
|
880
1378
|
const methodInfo = analyzeMethod(member, checker);
|
|
881
1379
|
if (methodInfo) {
|
|
882
|
-
const isStatic = member.modifiers?.some((m) => m.kind ===
|
|
1380
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts4.SyntaxKind.StaticKeyword);
|
|
883
1381
|
if (isStatic) {
|
|
884
1382
|
staticMethods.push(methodInfo);
|
|
885
1383
|
} else {
|
|
@@ -888,159 +1386,29 @@ function analyzeClass(classDecl, checker) {
|
|
|
888
1386
|
}
|
|
889
1387
|
}
|
|
890
1388
|
}
|
|
891
|
-
return {
|
|
892
|
-
name,
|
|
893
|
-
fields,
|
|
894
|
-
instanceMethods,
|
|
895
|
-
staticMethods
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
function analyzeField(prop, checker) {
|
|
899
|
-
if (!ts3.isIdentifier(prop.name)) {
|
|
900
|
-
return null;
|
|
901
|
-
}
|
|
902
|
-
const name = prop.name.text;
|
|
903
|
-
const typeNode = prop.type;
|
|
904
|
-
const type = checker.getTypeAtLocation(prop);
|
|
905
|
-
const optional = prop.questionToken !== void 0;
|
|
906
|
-
const decorators = extractDecorators(prop);
|
|
907
|
-
for (const dec of decorators) {
|
|
908
|
-
if (dec.node) {
|
|
909
|
-
const resolved = resolveDecorator(dec.node, checker);
|
|
910
|
-
if (resolved) {
|
|
911
|
-
dec.resolved = resolved;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
if (prop.type) {
|
|
916
|
-
const aliasConstraints = extractTypeAliasConstraints(prop.type, checker);
|
|
917
|
-
decorators.push(...aliasConstraints);
|
|
918
|
-
}
|
|
919
|
-
const jsdocConstraints = extractJSDocConstraints(prop);
|
|
920
|
-
decorators.push(...jsdocConstraints);
|
|
921
|
-
const deprecated = hasDeprecatedTag(prop);
|
|
922
|
-
const defaultValue = extractDefaultValue(prop.initializer);
|
|
923
|
-
return {
|
|
924
|
-
name,
|
|
925
|
-
typeNode,
|
|
926
|
-
type,
|
|
927
|
-
optional,
|
|
928
|
-
decorators,
|
|
929
|
-
deprecated,
|
|
930
|
-
defaultValue
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
function extractTypeAliasConstraints(typeNode, checker) {
|
|
934
|
-
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
935
|
-
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
936
|
-
if (!symbol?.declarations) return [];
|
|
937
|
-
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
938
|
-
if (!aliasDecl) return [];
|
|
939
|
-
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
940
|
-
return extractJSDocConstraints(aliasDecl);
|
|
941
|
-
}
|
|
942
|
-
function hasDeprecatedTag(node) {
|
|
943
|
-
const jsDocTags = ts3.getJSDocTags(node);
|
|
944
|
-
return jsDocTags.some((tag) => tag.tagName.text === "deprecated");
|
|
945
|
-
}
|
|
946
|
-
function extractDefaultValue(initializer) {
|
|
947
|
-
if (!initializer) return void 0;
|
|
948
|
-
if (ts3.isStringLiteral(initializer)) {
|
|
949
|
-
return initializer.text;
|
|
950
|
-
}
|
|
951
|
-
if (ts3.isNumericLiteral(initializer)) {
|
|
952
|
-
return Number(initializer.text);
|
|
953
|
-
}
|
|
954
|
-
if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
|
|
955
|
-
return true;
|
|
956
|
-
}
|
|
957
|
-
if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
|
|
958
|
-
return false;
|
|
959
|
-
}
|
|
960
|
-
if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
|
|
961
|
-
return null;
|
|
962
|
-
}
|
|
963
|
-
if (ts3.isPrefixUnaryExpression(initializer)) {
|
|
964
|
-
if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
|
|
965
|
-
return -Number(initializer.operand.text);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
return void 0;
|
|
969
|
-
}
|
|
970
|
-
function analyzeMethod(method, checker) {
|
|
971
|
-
if (!ts3.isIdentifier(method.name)) {
|
|
972
|
-
return null;
|
|
973
|
-
}
|
|
974
|
-
const name = method.name.text;
|
|
975
|
-
const parameters = [];
|
|
976
|
-
for (const param of method.parameters) {
|
|
977
|
-
if (ts3.isIdentifier(param.name)) {
|
|
978
|
-
const paramInfo = analyzeParameter(param, checker);
|
|
979
|
-
parameters.push(paramInfo);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
const returnTypeNode = method.type;
|
|
983
|
-
const signature = checker.getSignatureFromDeclaration(method);
|
|
984
|
-
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getTypeAtLocation(method);
|
|
985
|
-
return {
|
|
986
|
-
name,
|
|
987
|
-
parameters,
|
|
988
|
-
returnTypeNode,
|
|
989
|
-
returnType
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
function analyzeParameter(param, checker) {
|
|
993
|
-
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
994
|
-
const typeNode = param.type;
|
|
995
|
-
const type = checker.getTypeAtLocation(param);
|
|
996
|
-
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
997
|
-
const optional = param.questionToken !== void 0 || param.initializer !== void 0;
|
|
998
|
-
return {
|
|
999
|
-
name,
|
|
1000
|
-
typeNode,
|
|
1001
|
-
type,
|
|
1002
|
-
formSpecExportName,
|
|
1003
|
-
optional
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
function detectFormSpecReference(typeNode) {
|
|
1007
|
-
if (!typeNode) return null;
|
|
1008
|
-
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
1009
|
-
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
1010
|
-
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
1011
|
-
const typeArg = typeNode.typeArguments?.[0];
|
|
1012
|
-
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
1013
|
-
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
1014
|
-
return typeArg.exprName.text;
|
|
1015
|
-
}
|
|
1016
|
-
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
1017
|
-
return typeArg.exprName.right.text;
|
|
1018
|
-
}
|
|
1019
|
-
return null;
|
|
1389
|
+
return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
|
|
1020
1390
|
}
|
|
1021
|
-
function
|
|
1391
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1022
1392
|
const name = interfaceDecl.name.text;
|
|
1023
1393
|
const fields = [];
|
|
1394
|
+
const typeRegistry = {};
|
|
1395
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1024
1396
|
for (const member of interfaceDecl.members) {
|
|
1025
|
-
if (
|
|
1026
|
-
const
|
|
1027
|
-
if (
|
|
1028
|
-
fields.push(
|
|
1397
|
+
if (ts4.isPropertySignature(member)) {
|
|
1398
|
+
const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
|
|
1399
|
+
if (fieldNode) {
|
|
1400
|
+
fields.push(fieldNode);
|
|
1029
1401
|
}
|
|
1030
1402
|
}
|
|
1031
1403
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
fields,
|
|
1035
|
-
instanceMethods: [],
|
|
1036
|
-
staticMethods: []
|
|
1037
|
-
};
|
|
1404
|
+
const fieldLayouts = fields.map(() => ({}));
|
|
1405
|
+
return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
|
|
1038
1406
|
}
|
|
1039
|
-
function
|
|
1040
|
-
if (!
|
|
1407
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1408
|
+
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
1041
1409
|
const sourceFile = typeAlias.getSourceFile();
|
|
1042
1410
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
1043
|
-
const kindDesc =
|
|
1411
|
+
const kindDesc = ts4.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
1044
1412
|
return {
|
|
1045
1413
|
ok: false,
|
|
1046
1414
|
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
@@ -1048,11 +1416,13 @@ function analyzeTypeAlias(typeAlias, checker) {
|
|
|
1048
1416
|
}
|
|
1049
1417
|
const name = typeAlias.name.text;
|
|
1050
1418
|
const fields = [];
|
|
1419
|
+
const typeRegistry = {};
|
|
1420
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1051
1421
|
for (const member of typeAlias.type.members) {
|
|
1052
|
-
if (
|
|
1053
|
-
const
|
|
1054
|
-
if (
|
|
1055
|
-
fields.push(
|
|
1422
|
+
if (ts4.isPropertySignature(member)) {
|
|
1423
|
+
const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
|
|
1424
|
+
if (fieldNode) {
|
|
1425
|
+
fields.push(fieldNode);
|
|
1056
1426
|
}
|
|
1057
1427
|
}
|
|
1058
1428
|
}
|
|
@@ -1061,529 +1431,376 @@ function analyzeTypeAlias(typeAlias, checker) {
|
|
|
1061
1431
|
analysis: {
|
|
1062
1432
|
name,
|
|
1063
1433
|
fields,
|
|
1434
|
+
fieldLayouts: fields.map(() => ({})),
|
|
1435
|
+
typeRegistry,
|
|
1064
1436
|
instanceMethods: [],
|
|
1065
1437
|
staticMethods: []
|
|
1066
1438
|
}
|
|
1067
1439
|
};
|
|
1068
1440
|
}
|
|
1069
|
-
function
|
|
1070
|
-
if (!
|
|
1441
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1442
|
+
if (!ts4.isIdentifier(prop.name)) {
|
|
1071
1443
|
return null;
|
|
1072
1444
|
}
|
|
1073
1445
|
const name = prop.name.text;
|
|
1074
|
-
const
|
|
1075
|
-
const type = checker.getTypeAtLocation(prop);
|
|
1446
|
+
const tsType = checker.getTypeAtLocation(prop);
|
|
1076
1447
|
const optional = prop.questionToken !== void 0;
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1448
|
+
const provenance = provenanceForNode(prop, file);
|
|
1449
|
+
const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1450
|
+
const constraints = [];
|
|
1451
|
+
if (prop.type) {
|
|
1452
|
+
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1453
|
+
}
|
|
1454
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1455
|
+
const annotations = [];
|
|
1456
|
+
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1457
|
+
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1458
|
+
if (defaultAnnotation) {
|
|
1459
|
+
annotations.push(defaultAnnotation);
|
|
1460
|
+
}
|
|
1089
1461
|
return {
|
|
1462
|
+
kind: "field",
|
|
1090
1463
|
name,
|
|
1091
|
-
typeNode,
|
|
1092
1464
|
type,
|
|
1093
|
-
optional,
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1465
|
+
required: !optional,
|
|
1466
|
+
constraints,
|
|
1467
|
+
annotations,
|
|
1468
|
+
provenance
|
|
1097
1469
|
};
|
|
1098
1470
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
"use strict";
|
|
1103
|
-
ts3 = __toESM(require("typescript"), 1);
|
|
1104
|
-
init_decorator_extractor();
|
|
1105
|
-
init_jsdoc_constraints();
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
|
-
|
|
1109
|
-
// src/analyzer/type-converter.ts
|
|
1110
|
-
function getNamedTypeFieldInfoMap(type, checker) {
|
|
1111
|
-
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1112
|
-
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1113
|
-
);
|
|
1114
|
-
for (const symbol of symbols) {
|
|
1115
|
-
const declarations = symbol.declarations;
|
|
1116
|
-
if (!declarations) continue;
|
|
1117
|
-
const classDecl = declarations.find(ts4.isClassDeclaration);
|
|
1118
|
-
if (classDecl) {
|
|
1119
|
-
const map = /* @__PURE__ */ new Map();
|
|
1120
|
-
for (const member of classDecl.members) {
|
|
1121
|
-
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1122
|
-
const fieldInfo = analyzeField(member, checker);
|
|
1123
|
-
if (fieldInfo) map.set(fieldInfo.name, fieldInfo);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
return map;
|
|
1127
|
-
}
|
|
1128
|
-
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1129
|
-
if (interfaceDecl) {
|
|
1130
|
-
return buildFieldInfoMapFromSignatures(interfaceDecl.members, checker);
|
|
1131
|
-
}
|
|
1132
|
-
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1133
|
-
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
1134
|
-
return buildFieldInfoMapFromSignatures(typeAliasDecl.type.members, checker);
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
return null;
|
|
1138
|
-
}
|
|
1139
|
-
function buildFieldInfoMapFromSignatures(members, checker) {
|
|
1140
|
-
const map = /* @__PURE__ */ new Map();
|
|
1141
|
-
for (const member of members) {
|
|
1142
|
-
if (ts4.isPropertySignature(member)) {
|
|
1143
|
-
const fieldInfo = analyzeInterfaceProperty(member, checker);
|
|
1144
|
-
if (fieldInfo) {
|
|
1145
|
-
map.set(fieldInfo.name, fieldInfo);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1471
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1472
|
+
if (!ts4.isIdentifier(prop.name)) {
|
|
1473
|
+
return null;
|
|
1148
1474
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
const
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1158
|
-
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1159
|
-
const fieldInfo = fieldInfoMap?.get(prop.name) ?? void 0;
|
|
1160
|
-
result.push({ name: prop.name, type: propType, optional, fieldInfo });
|
|
1475
|
+
const name = prop.name.text;
|
|
1476
|
+
const tsType = checker.getTypeAtLocation(prop);
|
|
1477
|
+
const optional = prop.questionToken !== void 0;
|
|
1478
|
+
const provenance = provenanceForNode(prop, file);
|
|
1479
|
+
const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1480
|
+
const constraints = [];
|
|
1481
|
+
if (prop.type) {
|
|
1482
|
+
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
1161
1483
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
return
|
|
1484
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1485
|
+
const annotations = [];
|
|
1486
|
+
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1487
|
+
return {
|
|
1488
|
+
kind: "field",
|
|
1489
|
+
name,
|
|
1490
|
+
type,
|
|
1491
|
+
required: !optional,
|
|
1492
|
+
constraints,
|
|
1493
|
+
annotations,
|
|
1494
|
+
provenance
|
|
1495
|
+
};
|
|
1166
1496
|
}
|
|
1167
|
-
function
|
|
1497
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
1168
1498
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1169
|
-
return {
|
|
1499
|
+
return { kind: "primitive", primitiveKind: "string" };
|
|
1170
1500
|
}
|
|
1171
1501
|
if (type.flags & ts4.TypeFlags.Number) {
|
|
1172
|
-
return {
|
|
1502
|
+
return { kind: "primitive", primitiveKind: "number" };
|
|
1173
1503
|
}
|
|
1174
1504
|
if (type.flags & ts4.TypeFlags.Boolean) {
|
|
1175
|
-
return {
|
|
1505
|
+
return { kind: "primitive", primitiveKind: "boolean" };
|
|
1176
1506
|
}
|
|
1177
1507
|
if (type.flags & ts4.TypeFlags.Null) {
|
|
1178
|
-
return {
|
|
1508
|
+
return { kind: "primitive", primitiveKind: "null" };
|
|
1179
1509
|
}
|
|
1180
1510
|
if (type.flags & ts4.TypeFlags.Undefined) {
|
|
1181
|
-
return {
|
|
1511
|
+
return { kind: "primitive", primitiveKind: "null" };
|
|
1182
1512
|
}
|
|
1183
1513
|
if (type.isStringLiteral()) {
|
|
1184
1514
|
return {
|
|
1185
|
-
|
|
1186
|
-
|
|
1515
|
+
kind: "enum",
|
|
1516
|
+
members: [{ value: type.value }]
|
|
1187
1517
|
};
|
|
1188
1518
|
}
|
|
1189
1519
|
if (type.isNumberLiteral()) {
|
|
1190
1520
|
return {
|
|
1191
|
-
|
|
1192
|
-
|
|
1521
|
+
kind: "enum",
|
|
1522
|
+
members: [{ value: type.value }]
|
|
1193
1523
|
};
|
|
1194
1524
|
}
|
|
1195
1525
|
if (type.isUnion()) {
|
|
1196
|
-
return
|
|
1526
|
+
return resolveUnionType(type, checker, file, typeRegistry, visiting);
|
|
1197
1527
|
}
|
|
1198
1528
|
if (checker.isArrayType(type)) {
|
|
1199
|
-
return
|
|
1529
|
+
return resolveArrayType(type, checker, file, typeRegistry, visiting);
|
|
1200
1530
|
}
|
|
1201
|
-
if (type
|
|
1202
|
-
return
|
|
1531
|
+
if (isObjectType(type)) {
|
|
1532
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
1203
1533
|
}
|
|
1204
|
-
return {
|
|
1534
|
+
return { kind: "primitive", primitiveKind: "string" };
|
|
1205
1535
|
}
|
|
1206
|
-
function
|
|
1207
|
-
const
|
|
1208
|
-
const nonNullTypes =
|
|
1536
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
1537
|
+
const allTypes = type.types;
|
|
1538
|
+
const nonNullTypes = allTypes.filter(
|
|
1209
1539
|
(t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1210
1540
|
);
|
|
1211
|
-
const hasNull =
|
|
1212
|
-
const
|
|
1213
|
-
if (
|
|
1214
|
-
const
|
|
1215
|
-
jsonSchema: { type: "boolean" },
|
|
1216
|
-
formSpecFieldType: "boolean"
|
|
1217
|
-
};
|
|
1541
|
+
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
1542
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1543
|
+
if (isBooleanUnion2) {
|
|
1544
|
+
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1218
1545
|
if (hasNull) {
|
|
1219
|
-
|
|
1546
|
+
return {
|
|
1547
|
+
kind: "union",
|
|
1548
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1549
|
+
};
|
|
1220
1550
|
}
|
|
1221
|
-
return
|
|
1551
|
+
return boolNode;
|
|
1222
1552
|
}
|
|
1223
1553
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1224
1554
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1225
|
-
const
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1228
|
-
|
|
1555
|
+
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1556
|
+
const enumNode = {
|
|
1557
|
+
kind: "enum",
|
|
1558
|
+
members: stringTypes.map((t) => ({ value: t.value }))
|
|
1229
1559
|
};
|
|
1230
1560
|
if (hasNull) {
|
|
1231
|
-
|
|
1561
|
+
return {
|
|
1562
|
+
kind: "union",
|
|
1563
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1564
|
+
};
|
|
1232
1565
|
}
|
|
1233
|
-
return
|
|
1566
|
+
return enumNode;
|
|
1234
1567
|
}
|
|
1235
1568
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1236
1569
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1237
|
-
const
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1570
|
+
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1571
|
+
const enumNode = {
|
|
1572
|
+
kind: "enum",
|
|
1573
|
+
members: numberTypes.map((t) => ({ value: t.value }))
|
|
1241
1574
|
};
|
|
1242
1575
|
if (hasNull) {
|
|
1243
|
-
|
|
1576
|
+
return {
|
|
1577
|
+
kind: "union",
|
|
1578
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1579
|
+
};
|
|
1244
1580
|
}
|
|
1245
|
-
return
|
|
1581
|
+
return enumNode;
|
|
1246
1582
|
}
|
|
1247
1583
|
if (nonNullTypes.length === 1 && nonNullTypes[0]) {
|
|
1248
|
-
const
|
|
1584
|
+
const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
|
|
1249
1585
|
if (hasNull) {
|
|
1250
|
-
|
|
1586
|
+
return {
|
|
1587
|
+
kind: "union",
|
|
1588
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
1589
|
+
};
|
|
1251
1590
|
}
|
|
1252
|
-
return
|
|
1591
|
+
return inner;
|
|
1253
1592
|
}
|
|
1254
|
-
const
|
|
1593
|
+
const members = nonNullTypes.map(
|
|
1594
|
+
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
|
|
1595
|
+
);
|
|
1255
1596
|
if (hasNull) {
|
|
1256
|
-
|
|
1597
|
+
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1257
1598
|
}
|
|
1258
|
-
return {
|
|
1259
|
-
jsonSchema: { oneOf: schemas },
|
|
1260
|
-
formSpecFieldType: "union"
|
|
1261
|
-
};
|
|
1599
|
+
return { kind: "union", members };
|
|
1262
1600
|
}
|
|
1263
|
-
function
|
|
1264
|
-
const typeArgs = type.typeArguments;
|
|
1601
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1602
|
+
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1265
1603
|
const elementType = typeArgs?.[0];
|
|
1266
|
-
const
|
|
1267
|
-
return {
|
|
1268
|
-
jsonSchema: {
|
|
1269
|
-
type: "array",
|
|
1270
|
-
items: itemSchema
|
|
1271
|
-
},
|
|
1272
|
-
formSpecFieldType: "array"
|
|
1273
|
-
};
|
|
1604
|
+
const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
|
|
1605
|
+
return { kind: "array", items };
|
|
1274
1606
|
}
|
|
1275
|
-
function
|
|
1607
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1276
1608
|
if (visiting.has(type)) {
|
|
1277
|
-
return {
|
|
1609
|
+
return { kind: "object", properties: [], additionalProperties: false };
|
|
1278
1610
|
}
|
|
1279
1611
|
visiting.add(type);
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1612
|
+
const typeName = getNamedTypeName(type);
|
|
1613
|
+
if (typeName && typeName in typeRegistry) {
|
|
1614
|
+
visiting.delete(type);
|
|
1615
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1616
|
+
}
|
|
1617
|
+
const properties = [];
|
|
1618
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
|
|
1619
|
+
for (const prop of type.getProperties()) {
|
|
1620
|
+
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1621
|
+
if (!declaration) continue;
|
|
1622
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1623
|
+
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1624
|
+
const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
|
|
1625
|
+
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1626
|
+
properties.push({
|
|
1627
|
+
name: prop.name,
|
|
1628
|
+
type: propTypeNode,
|
|
1629
|
+
optional,
|
|
1630
|
+
constraints: fieldNodeInfo?.constraints ?? [],
|
|
1631
|
+
annotations: fieldNodeInfo?.annotations ?? [],
|
|
1632
|
+
provenance: fieldNodeInfo?.provenance ?? provenanceForFile(file)
|
|
1633
|
+
});
|
|
1288
1634
|
}
|
|
1289
1635
|
visiting.delete(type);
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
...required.length > 0 ? { required } : {}
|
|
1295
|
-
},
|
|
1296
|
-
formSpecFieldType: "object"
|
|
1297
|
-
};
|
|
1298
|
-
}
|
|
1299
|
-
function createFormSpecField(fieldName, type, decorators, optional, checker, visitedTypes = /* @__PURE__ */ new Set()) {
|
|
1300
|
-
const { formSpecFieldType } = convertType(type, checker);
|
|
1301
|
-
const field = {
|
|
1302
|
-
_field: formSpecFieldType,
|
|
1303
|
-
id: fieldName
|
|
1636
|
+
const objectNode = {
|
|
1637
|
+
kind: "object",
|
|
1638
|
+
properties,
|
|
1639
|
+
additionalProperties: false
|
|
1304
1640
|
};
|
|
1305
|
-
if (
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
for (const propInfo of getObjectPropertyInfos(type, checker)) {
|
|
1313
|
-
nestedFields.push(
|
|
1314
|
-
createFormSpecField(
|
|
1315
|
-
propInfo.name,
|
|
1316
|
-
propInfo.type,
|
|
1317
|
-
propInfo.fieldInfo?.decorators ?? [],
|
|
1318
|
-
propInfo.optional,
|
|
1319
|
-
checker,
|
|
1320
|
-
visitedTypes
|
|
1321
|
-
)
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1324
|
-
visitedTypes.delete(type);
|
|
1325
|
-
if (nestedFields.length > 0) {
|
|
1326
|
-
field.fields = nestedFields;
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1641
|
+
if (typeName) {
|
|
1642
|
+
typeRegistry[typeName] = {
|
|
1643
|
+
name: typeName,
|
|
1644
|
+
type: objectNode,
|
|
1645
|
+
provenance: provenanceForFile(file)
|
|
1646
|
+
};
|
|
1647
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1329
1648
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
if (
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
break;
|
|
1354
|
-
}
|
|
1355
|
-
case "Minimum":
|
|
1356
|
-
if (typeof args[0] === "number") {
|
|
1357
|
-
field.min = args[0];
|
|
1358
|
-
}
|
|
1359
|
-
break;
|
|
1360
|
-
case "Maximum":
|
|
1361
|
-
if (typeof args[0] === "number") {
|
|
1362
|
-
field.max = args[0];
|
|
1363
|
-
}
|
|
1364
|
-
break;
|
|
1365
|
-
case "MinLength":
|
|
1366
|
-
if (typeof args[0] === "number") {
|
|
1367
|
-
field.minLength = args[0];
|
|
1368
|
-
}
|
|
1369
|
-
break;
|
|
1370
|
-
case "MaxLength":
|
|
1371
|
-
if (typeof args[0] === "number") {
|
|
1372
|
-
field.maxLength = args[0];
|
|
1373
|
-
}
|
|
1374
|
-
break;
|
|
1375
|
-
case "Pattern":
|
|
1376
|
-
if (typeof args[0] === "string") {
|
|
1377
|
-
field.pattern = args[0];
|
|
1378
|
-
}
|
|
1379
|
-
break;
|
|
1380
|
-
case "EnumOptions":
|
|
1381
|
-
if (Array.isArray(args[0])) {
|
|
1382
|
-
field.options = args[0];
|
|
1383
|
-
}
|
|
1384
|
-
break;
|
|
1385
|
-
case "ShowWhen":
|
|
1386
|
-
if (typeof args[0] === "object" && args[0] !== null) {
|
|
1387
|
-
field.showWhen = args[0];
|
|
1388
|
-
}
|
|
1389
|
-
break;
|
|
1390
|
-
case "Group":
|
|
1391
|
-
if (typeof args[0] === "string") {
|
|
1392
|
-
field.group = args[0];
|
|
1393
|
-
}
|
|
1394
|
-
break;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
function applyDecoratorsToSchema(schema, decorators, fieldInfo) {
|
|
1398
|
-
const result = { ...schema };
|
|
1399
|
-
for (const dec of decorators) {
|
|
1400
|
-
const { args } = dec;
|
|
1401
|
-
const resolved = dec.resolved;
|
|
1402
|
-
const effectiveName = resolved?.extendsBuiltin ?? dec.name;
|
|
1403
|
-
switch (effectiveName) {
|
|
1404
|
-
case "Field": {
|
|
1405
|
-
const opts = args[0];
|
|
1406
|
-
if (typeof opts === "object" && opts !== null && !Array.isArray(opts)) {
|
|
1407
|
-
if (typeof opts["displayName"] === "string") {
|
|
1408
|
-
result.title = opts["displayName"];
|
|
1409
|
-
}
|
|
1410
|
-
if (typeof opts["description"] === "string") {
|
|
1411
|
-
result.description = opts["description"];
|
|
1649
|
+
return objectNode;
|
|
1650
|
+
}
|
|
1651
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
1652
|
+
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1653
|
+
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1654
|
+
);
|
|
1655
|
+
for (const symbol of symbols) {
|
|
1656
|
+
const declarations = symbol.declarations;
|
|
1657
|
+
if (!declarations) continue;
|
|
1658
|
+
const classDecl = declarations.find(ts4.isClassDeclaration);
|
|
1659
|
+
if (classDecl) {
|
|
1660
|
+
const map = /* @__PURE__ */ new Map();
|
|
1661
|
+
for (const member of classDecl.members) {
|
|
1662
|
+
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1663
|
+
const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
|
|
1664
|
+
if (fieldNode) {
|
|
1665
|
+
map.set(fieldNode.name, {
|
|
1666
|
+
constraints: [...fieldNode.constraints],
|
|
1667
|
+
annotations: [...fieldNode.annotations],
|
|
1668
|
+
provenance: fieldNode.provenance
|
|
1669
|
+
});
|
|
1412
1670
|
}
|
|
1413
1671
|
}
|
|
1414
|
-
break;
|
|
1415
|
-
}
|
|
1416
|
-
case "Minimum":
|
|
1417
|
-
if (typeof args[0] === "number") {
|
|
1418
|
-
result.minimum = args[0];
|
|
1419
|
-
}
|
|
1420
|
-
break;
|
|
1421
|
-
case "Maximum":
|
|
1422
|
-
if (typeof args[0] === "number") {
|
|
1423
|
-
result.maximum = args[0];
|
|
1424
|
-
}
|
|
1425
|
-
break;
|
|
1426
|
-
case "ExclusiveMinimum":
|
|
1427
|
-
if (typeof args[0] === "number") {
|
|
1428
|
-
result.exclusiveMinimum = args[0];
|
|
1429
|
-
}
|
|
1430
|
-
break;
|
|
1431
|
-
case "ExclusiveMaximum":
|
|
1432
|
-
if (typeof args[0] === "number") {
|
|
1433
|
-
result.exclusiveMaximum = args[0];
|
|
1434
|
-
}
|
|
1435
|
-
break;
|
|
1436
|
-
case "MinLength":
|
|
1437
|
-
if (typeof args[0] === "number") {
|
|
1438
|
-
result.minLength = args[0];
|
|
1439
|
-
}
|
|
1440
|
-
break;
|
|
1441
|
-
case "MaxLength":
|
|
1442
|
-
if (typeof args[0] === "number") {
|
|
1443
|
-
result.maxLength = args[0];
|
|
1444
|
-
}
|
|
1445
|
-
break;
|
|
1446
|
-
case "Pattern":
|
|
1447
|
-
if (typeof args[0] === "string") {
|
|
1448
|
-
result.pattern = args[0];
|
|
1449
|
-
}
|
|
1450
|
-
break;
|
|
1451
|
-
}
|
|
1452
|
-
if (resolved?.extensionName && /^[a-z][a-z0-9-]*$/.test(resolved.extensionName)) {
|
|
1453
|
-
const key = `x-formspec-${resolved.extensionName}`;
|
|
1454
|
-
if (resolved.isMarker) {
|
|
1455
|
-
setSchemaExtension(result, key, true);
|
|
1456
|
-
} else {
|
|
1457
|
-
setSchemaExtension(result, key, args[0] ?? true);
|
|
1458
1672
|
}
|
|
1673
|
+
return map;
|
|
1459
1674
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
result.deprecated = true;
|
|
1675
|
+
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1676
|
+
if (interfaceDecl) {
|
|
1677
|
+
return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
|
|
1464
1678
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1679
|
+
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1680
|
+
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
1681
|
+
return buildFieldNodeInfoMap(
|
|
1682
|
+
typeAliasDecl.type.members,
|
|
1683
|
+
checker,
|
|
1684
|
+
file,
|
|
1685
|
+
typeRegistry,
|
|
1686
|
+
visiting
|
|
1687
|
+
);
|
|
1467
1688
|
}
|
|
1468
1689
|
}
|
|
1469
|
-
return
|
|
1690
|
+
return null;
|
|
1470
1691
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
const absolutePath = path.resolve(filePath);
|
|
1484
|
-
const fileDir = path.dirname(absolutePath);
|
|
1485
|
-
const configPath = ts5.findConfigFile(fileDir, ts5.sys.fileExists.bind(ts5.sys), "tsconfig.json");
|
|
1486
|
-
let compilerOptions;
|
|
1487
|
-
let fileNames;
|
|
1488
|
-
if (configPath) {
|
|
1489
|
-
const configFile = ts5.readConfigFile(configPath, ts5.sys.readFile.bind(ts5.sys));
|
|
1490
|
-
if (configFile.error) {
|
|
1491
|
-
throw new Error(
|
|
1492
|
-
`Error reading tsconfig.json: ${ts5.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
1493
|
-
);
|
|
1494
|
-
}
|
|
1495
|
-
const parsed = ts5.parseJsonConfigFileContent(
|
|
1496
|
-
configFile.config,
|
|
1497
|
-
ts5.sys,
|
|
1498
|
-
path.dirname(configPath)
|
|
1499
|
-
);
|
|
1500
|
-
if (parsed.errors.length > 0) {
|
|
1501
|
-
const errorMessages = parsed.errors.map((e) => ts5.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
1502
|
-
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
1692
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
1693
|
+
const map = /* @__PURE__ */ new Map();
|
|
1694
|
+
for (const member of members) {
|
|
1695
|
+
if (ts4.isPropertySignature(member)) {
|
|
1696
|
+
const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
|
|
1697
|
+
if (fieldNode) {
|
|
1698
|
+
map.set(fieldNode.name, {
|
|
1699
|
+
constraints: [...fieldNode.constraints],
|
|
1700
|
+
annotations: [...fieldNode.annotations],
|
|
1701
|
+
provenance: fieldNode.provenance
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1503
1704
|
}
|
|
1504
|
-
compilerOptions = parsed.options;
|
|
1505
|
-
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
1506
|
-
} else {
|
|
1507
|
-
compilerOptions = {
|
|
1508
|
-
target: ts5.ScriptTarget.ES2022,
|
|
1509
|
-
module: ts5.ModuleKind.NodeNext,
|
|
1510
|
-
moduleResolution: ts5.ModuleResolutionKind.NodeNext,
|
|
1511
|
-
strict: true,
|
|
1512
|
-
skipLibCheck: true,
|
|
1513
|
-
declaration: true
|
|
1514
|
-
};
|
|
1515
|
-
fileNames = [absolutePath];
|
|
1516
|
-
}
|
|
1517
|
-
const program = ts5.createProgram(fileNames, compilerOptions);
|
|
1518
|
-
const sourceFile = program.getSourceFile(absolutePath);
|
|
1519
|
-
if (!sourceFile) {
|
|
1520
|
-
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
1521
1705
|
}
|
|
1706
|
+
return map;
|
|
1707
|
+
}
|
|
1708
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file) {
|
|
1709
|
+
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1710
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1711
|
+
if (!symbol?.declarations) return [];
|
|
1712
|
+
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1713
|
+
if (!aliasDecl) return [];
|
|
1714
|
+
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1715
|
+
return extractJSDocConstraintNodes(aliasDecl, file);
|
|
1716
|
+
}
|
|
1717
|
+
function provenanceForNode(node, file) {
|
|
1718
|
+
const sourceFile = node.getSourceFile();
|
|
1719
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
1522
1720
|
return {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1721
|
+
surface: "tsdoc",
|
|
1722
|
+
file,
|
|
1723
|
+
line: line + 1,
|
|
1724
|
+
column: character
|
|
1526
1725
|
};
|
|
1527
1726
|
}
|
|
1528
|
-
function
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1727
|
+
function provenanceForFile(file) {
|
|
1728
|
+
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1729
|
+
}
|
|
1730
|
+
function getNamedTypeName(type) {
|
|
1731
|
+
const symbol = type.getSymbol();
|
|
1732
|
+
if (symbol?.declarations) {
|
|
1733
|
+
const decl = symbol.declarations[0];
|
|
1734
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
1735
|
+
const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
1736
|
+
if (name) return name;
|
|
1535
1737
|
}
|
|
1536
|
-
ts5.forEachChild(node, visit);
|
|
1537
1738
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1739
|
+
const aliasSymbol = type.aliasSymbol;
|
|
1740
|
+
if (aliasSymbol?.declarations) {
|
|
1741
|
+
const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1742
|
+
if (aliasDecl) {
|
|
1743
|
+
return aliasDecl.name.text;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return null;
|
|
1540
1747
|
}
|
|
1541
|
-
function
|
|
1542
|
-
|
|
1748
|
+
function analyzeMethod(method, checker) {
|
|
1749
|
+
if (!ts4.isIdentifier(method.name)) {
|
|
1750
|
+
return null;
|
|
1751
|
+
}
|
|
1752
|
+
const name = method.name.text;
|
|
1753
|
+
const parameters = [];
|
|
1754
|
+
for (const param of method.parameters) {
|
|
1755
|
+
if (ts4.isIdentifier(param.name)) {
|
|
1756
|
+
const paramInfo = analyzeParameter(param, checker);
|
|
1757
|
+
parameters.push(paramInfo);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
const returnTypeNode = method.type;
|
|
1761
|
+
const signature = checker.getSignatureFromDeclaration(method);
|
|
1762
|
+
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getTypeAtLocation(method);
|
|
1763
|
+
return { name, parameters, returnTypeNode, returnType };
|
|
1543
1764
|
}
|
|
1544
|
-
function
|
|
1545
|
-
|
|
1765
|
+
function analyzeParameter(param, checker) {
|
|
1766
|
+
const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
|
|
1767
|
+
const typeNode = param.type;
|
|
1768
|
+
const type = checker.getTypeAtLocation(param);
|
|
1769
|
+
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
1770
|
+
const optional = param.questionToken !== void 0 || param.initializer !== void 0;
|
|
1771
|
+
return { name, typeNode, type, formSpecExportName, optional };
|
|
1546
1772
|
}
|
|
1547
|
-
function
|
|
1548
|
-
|
|
1773
|
+
function detectFormSpecReference(typeNode) {
|
|
1774
|
+
if (!typeNode) return null;
|
|
1775
|
+
if (!ts4.isTypeReferenceNode(typeNode)) return null;
|
|
1776
|
+
const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
1777
|
+
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
1778
|
+
const typeArg = typeNode.typeArguments?.[0];
|
|
1779
|
+
if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
|
|
1780
|
+
if (ts4.isIdentifier(typeArg.exprName)) {
|
|
1781
|
+
return typeArg.exprName.text;
|
|
1782
|
+
}
|
|
1783
|
+
if (ts4.isQualifiedName(typeArg.exprName)) {
|
|
1784
|
+
return typeArg.exprName.right.text;
|
|
1785
|
+
}
|
|
1786
|
+
return null;
|
|
1549
1787
|
}
|
|
1550
|
-
var
|
|
1551
|
-
var
|
|
1552
|
-
"src/analyzer/
|
|
1788
|
+
var ts4;
|
|
1789
|
+
var init_class_analyzer = __esm({
|
|
1790
|
+
"src/analyzer/class-analyzer.ts"() {
|
|
1553
1791
|
"use strict";
|
|
1554
|
-
|
|
1555
|
-
|
|
1792
|
+
ts4 = __toESM(require("typescript"), 1);
|
|
1793
|
+
init_jsdoc_constraints();
|
|
1556
1794
|
}
|
|
1557
1795
|
});
|
|
1558
1796
|
|
|
1559
1797
|
// src/generators/class-schema.ts
|
|
1560
|
-
function generateClassSchemas(analysis,
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
const { jsonSchema: baseSchema } = convertType(field.type, checker);
|
|
1566
|
-
const fieldSchema = applyDecoratorsToSchema(baseSchema, field.decorators, field);
|
|
1567
|
-
properties[field.name] = fieldSchema;
|
|
1568
|
-
if (!field.optional) {
|
|
1569
|
-
required.push(field.name);
|
|
1570
|
-
}
|
|
1571
|
-
const formSpecField = createFormSpecField(
|
|
1572
|
-
field.name,
|
|
1573
|
-
field.type,
|
|
1574
|
-
field.decorators,
|
|
1575
|
-
field.optional,
|
|
1576
|
-
checker
|
|
1577
|
-
);
|
|
1578
|
-
uiElements.push(formSpecField);
|
|
1579
|
-
}
|
|
1580
|
-
const jsonSchema = {
|
|
1581
|
-
type: "object",
|
|
1582
|
-
properties,
|
|
1583
|
-
...required.length > 0 ? { required } : {}
|
|
1798
|
+
function generateClassSchemas(analysis, source) {
|
|
1799
|
+
const ir = canonicalizeTSDoc(analysis, source);
|
|
1800
|
+
return {
|
|
1801
|
+
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
1802
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
1584
1803
|
};
|
|
1585
|
-
const uiSchema2 = generateUiSchemaFromFields(uiElements);
|
|
1586
|
-
return { jsonSchema, uiSchema: uiSchema2 };
|
|
1587
1804
|
}
|
|
1588
1805
|
function generateSchemasFromClass(options) {
|
|
1589
1806
|
const ctx = createProgramContext(options.filePath);
|
|
@@ -1591,26 +1808,27 @@ function generateSchemasFromClass(options) {
|
|
|
1591
1808
|
if (!classDecl) {
|
|
1592
1809
|
throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
|
|
1593
1810
|
}
|
|
1594
|
-
const analysis =
|
|
1595
|
-
return generateClassSchemas(analysis,
|
|
1811
|
+
const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
|
|
1812
|
+
return generateClassSchemas(analysis, { file: options.filePath });
|
|
1596
1813
|
}
|
|
1597
1814
|
function generateSchemas(options) {
|
|
1598
1815
|
const ctx = createProgramContext(options.filePath);
|
|
1816
|
+
const source = { file: options.filePath };
|
|
1599
1817
|
const classDecl = findClassByName(ctx.sourceFile, options.typeName);
|
|
1600
1818
|
if (classDecl) {
|
|
1601
|
-
const analysis =
|
|
1602
|
-
return generateClassSchemas(analysis,
|
|
1819
|
+
const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
|
|
1820
|
+
return generateClassSchemas(analysis, source);
|
|
1603
1821
|
}
|
|
1604
1822
|
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
1605
1823
|
if (interfaceDecl) {
|
|
1606
|
-
const analysis =
|
|
1607
|
-
return generateClassSchemas(analysis,
|
|
1824
|
+
const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
|
|
1825
|
+
return generateClassSchemas(analysis, source);
|
|
1608
1826
|
}
|
|
1609
1827
|
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
1610
1828
|
if (typeAlias) {
|
|
1611
|
-
const result =
|
|
1829
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
|
|
1612
1830
|
if (result.ok) {
|
|
1613
|
-
return generateClassSchemas(result.analysis,
|
|
1831
|
+
return generateClassSchemas(result.analysis, source);
|
|
1614
1832
|
}
|
|
1615
1833
|
throw new Error(result.error);
|
|
1616
1834
|
}
|
|
@@ -1621,463 +1839,11 @@ function generateSchemas(options) {
|
|
|
1621
1839
|
var init_class_schema = __esm({
|
|
1622
1840
|
"src/generators/class-schema.ts"() {
|
|
1623
1841
|
"use strict";
|
|
1624
|
-
init_type_converter();
|
|
1625
|
-
init_generator2();
|
|
1626
1842
|
init_program();
|
|
1627
1843
|
init_class_analyzer();
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
// src/codegen/index.ts
|
|
1632
|
-
function findDecoratedClasses(files, baseDir) {
|
|
1633
|
-
const program = ts6.createProgram(files, {
|
|
1634
|
-
target: ts6.ScriptTarget.ESNext,
|
|
1635
|
-
module: ts6.ModuleKind.ESNext,
|
|
1636
|
-
moduleResolution: ts6.ModuleResolutionKind.NodeNext,
|
|
1637
|
-
// TC39 Stage 3 decorators — do not use experimentalDecorators
|
|
1638
|
-
strict: true
|
|
1639
|
-
});
|
|
1640
|
-
const checker = program.getTypeChecker();
|
|
1641
|
-
const results = [];
|
|
1642
|
-
for (const sourceFile of program.getSourceFiles()) {
|
|
1643
|
-
if (sourceFile.isDeclarationFile) continue;
|
|
1644
|
-
if (!files.some((f) => path2.resolve(f) === sourceFile.fileName)) continue;
|
|
1645
|
-
ts6.forEachChild(sourceFile, (node) => {
|
|
1646
|
-
if (ts6.isClassDeclaration(node) && hasDecoratedProperties(node)) {
|
|
1647
|
-
const className = node.name?.text;
|
|
1648
|
-
if (!className) return;
|
|
1649
|
-
const typeMetadata = extractTypeMetadata(node, checker);
|
|
1650
|
-
const relativePath = path2.relative(baseDir, sourceFile.fileName);
|
|
1651
|
-
const exported = isClassExported(node, sourceFile);
|
|
1652
|
-
results.push({
|
|
1653
|
-
name: className,
|
|
1654
|
-
sourcePath: relativePath.replace(/\.tsx?$/, ""),
|
|
1655
|
-
typeMetadata,
|
|
1656
|
-
isExported: exported
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
});
|
|
1660
|
-
}
|
|
1661
|
-
return results;
|
|
1662
|
-
}
|
|
1663
|
-
function hasDecoratedProperties(node) {
|
|
1664
|
-
return node.members.some((member) => {
|
|
1665
|
-
if (!ts6.isPropertyDeclaration(member)) return false;
|
|
1666
|
-
const decorators = ts6.getDecorators(member);
|
|
1667
|
-
return decorators !== void 0 && decorators.length > 0;
|
|
1668
|
-
});
|
|
1669
|
-
}
|
|
1670
|
-
function isClassExported(classNode, sourceFile) {
|
|
1671
|
-
const className = classNode.name?.text;
|
|
1672
|
-
if (!className) return false;
|
|
1673
|
-
const modifiers = ts6.getModifiers(classNode);
|
|
1674
|
-
if (modifiers) {
|
|
1675
|
-
const hasExport = modifiers.some((mod) => mod.kind === ts6.SyntaxKind.ExportKeyword);
|
|
1676
|
-
if (hasExport) return true;
|
|
1677
|
-
}
|
|
1678
|
-
for (const statement of sourceFile.statements) {
|
|
1679
|
-
if (ts6.isExportDeclaration(statement) && statement.exportClause) {
|
|
1680
|
-
if (ts6.isNamedExports(statement.exportClause)) {
|
|
1681
|
-
for (const element of statement.exportClause.elements) {
|
|
1682
|
-
const localName = element.propertyName?.text ?? element.name.text;
|
|
1683
|
-
if (localName === className) {
|
|
1684
|
-
return true;
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
return false;
|
|
1691
|
-
}
|
|
1692
|
-
function extractTypeMetadata(classNode, checker) {
|
|
1693
|
-
const metadata = {};
|
|
1694
|
-
for (const member of classNode.members) {
|
|
1695
|
-
if (!ts6.isPropertyDeclaration(member)) continue;
|
|
1696
|
-
if (!member.name || !ts6.isIdentifier(member.name)) continue;
|
|
1697
|
-
const symbol = checker.getSymbolAtLocation(member.name);
|
|
1698
|
-
if (!symbol) continue;
|
|
1699
|
-
const type = checker.getTypeOfSymbolAtLocation(symbol, member);
|
|
1700
|
-
const fieldName = member.name.text;
|
|
1701
|
-
const isOptional = !!(symbol.flags & ts6.SymbolFlags.Optional);
|
|
1702
|
-
const typeInfo = convertTypeToMetadata(type, checker);
|
|
1703
|
-
if (isOptional) {
|
|
1704
|
-
typeInfo.optional = true;
|
|
1705
|
-
}
|
|
1706
|
-
metadata[fieldName] = typeInfo;
|
|
1707
|
-
}
|
|
1708
|
-
return metadata;
|
|
1709
|
-
}
|
|
1710
|
-
function convertTypeToMetadata(type, checker, visited = /* @__PURE__ */ new Set()) {
|
|
1711
|
-
const isObjectType = (type.flags & ts6.TypeFlags.Object) !== 0;
|
|
1712
|
-
if (isObjectType) {
|
|
1713
|
-
if (visited.has(type)) {
|
|
1714
|
-
return { type: "unknown" };
|
|
1715
|
-
}
|
|
1716
|
-
visited.add(type);
|
|
1717
|
-
}
|
|
1718
|
-
if (type.isUnion()) {
|
|
1719
|
-
const types = type.types;
|
|
1720
|
-
const nonNullTypes = types.filter(
|
|
1721
|
-
(t) => !(t.flags & (ts6.TypeFlags.Null | ts6.TypeFlags.Undefined))
|
|
1722
|
-
);
|
|
1723
|
-
const hasNull = types.some((t) => t.flags & ts6.TypeFlags.Null);
|
|
1724
|
-
const isBooleanUnion = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts6.TypeFlags.BooleanLiteral);
|
|
1725
|
-
if (isBooleanUnion) {
|
|
1726
|
-
const result = { type: "boolean" };
|
|
1727
|
-
if (hasNull) result.nullable = true;
|
|
1728
|
-
return result;
|
|
1729
|
-
}
|
|
1730
|
-
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1731
|
-
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1732
|
-
const result = {
|
|
1733
|
-
type: "enum",
|
|
1734
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TypeScript doesn't narrow array types from `every` predicate
|
|
1735
|
-
values: nonNullTypes.map((t) => t.value)
|
|
1736
|
-
};
|
|
1737
|
-
if (hasNull) result.nullable = true;
|
|
1738
|
-
return result;
|
|
1739
|
-
}
|
|
1740
|
-
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1741
|
-
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1742
|
-
const result = {
|
|
1743
|
-
type: "enum",
|
|
1744
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TypeScript doesn't narrow array types from `every` predicate
|
|
1745
|
-
values: nonNullTypes.map((t) => t.value)
|
|
1746
|
-
};
|
|
1747
|
-
if (hasNull) result.nullable = true;
|
|
1748
|
-
return result;
|
|
1749
|
-
}
|
|
1750
|
-
if (nonNullTypes.length === 1) {
|
|
1751
|
-
const singleType = nonNullTypes[0];
|
|
1752
|
-
if (!singleType) return { type: "unknown" };
|
|
1753
|
-
const result = convertTypeToMetadata(singleType, checker, visited);
|
|
1754
|
-
if (hasNull) result.nullable = true;
|
|
1755
|
-
return result;
|
|
1756
|
-
}
|
|
1757
|
-
return { type: "unknown" };
|
|
1758
|
-
}
|
|
1759
|
-
if (type.flags & ts6.TypeFlags.String) return { type: "string" };
|
|
1760
|
-
if (type.flags & ts6.TypeFlags.Number) return { type: "number" };
|
|
1761
|
-
if (type.flags & ts6.TypeFlags.Boolean) return { type: "boolean" };
|
|
1762
|
-
if (type.isStringLiteral()) {
|
|
1763
|
-
return { type: "enum", values: [type.value] };
|
|
1764
|
-
}
|
|
1765
|
-
if (type.isNumberLiteral()) {
|
|
1766
|
-
return { type: "enum", values: [type.value] };
|
|
1767
|
-
}
|
|
1768
|
-
if (checker.isArrayType(type)) {
|
|
1769
|
-
const typeArgs = type.typeArguments;
|
|
1770
|
-
return {
|
|
1771
|
-
type: "array",
|
|
1772
|
-
itemType: typeArgs?.[0] ? convertTypeToMetadata(typeArgs[0], checker, visited) : { type: "unknown" }
|
|
1773
|
-
};
|
|
1774
|
-
}
|
|
1775
|
-
if (type.flags & ts6.TypeFlags.Object) {
|
|
1776
|
-
const properties = {};
|
|
1777
|
-
for (const prop of type.getProperties()) {
|
|
1778
|
-
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1779
|
-
if (!declaration) continue;
|
|
1780
|
-
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1781
|
-
const propOptional = !!(prop.flags & ts6.SymbolFlags.Optional);
|
|
1782
|
-
const propMeta = convertTypeToMetadata(propType, checker, visited);
|
|
1783
|
-
if (propOptional) propMeta.optional = true;
|
|
1784
|
-
properties[prop.name] = propMeta;
|
|
1785
|
-
}
|
|
1786
|
-
if (Object.keys(properties).length > 0) {
|
|
1787
|
-
return { type: "object", properties };
|
|
1788
|
-
}
|
|
1789
|
-
return { type: "object" };
|
|
1790
|
-
}
|
|
1791
|
-
return { type: "unknown" };
|
|
1792
|
-
}
|
|
1793
|
-
function generateCodegenOutput(classes, outputPath, baseDir) {
|
|
1794
|
-
const outputDir = path2.dirname(path2.resolve(outputPath));
|
|
1795
|
-
const lines = [
|
|
1796
|
-
"/**",
|
|
1797
|
-
" * Auto-generated by FormSpec CLI.",
|
|
1798
|
-
" * DO NOT EDIT - changes will be overwritten.",
|
|
1799
|
-
" *",
|
|
1800
|
-
" * This file provides:",
|
|
1801
|
-
" * - Type metadata patches for runtime schema generation",
|
|
1802
|
-
" * - Inferred schema types (e.g., UserFormSchema)",
|
|
1803
|
-
" * - Type-safe FormSpec accessors (e.g., getUserFormFormSpec())",
|
|
1804
|
-
" *",
|
|
1805
|
-
" * Usage:",
|
|
1806
|
-
" * import { UserFormSchema, getUserFormFormSpec } from './__formspec_types__';",
|
|
1807
|
-
" *",
|
|
1808
|
-
" * const data: UserFormSchema = { name: 'Alice', country: 'us' };",
|
|
1809
|
-
" * const spec = getUserFormFormSpec();",
|
|
1810
|
-
" */",
|
|
1811
|
-
"",
|
|
1812
|
-
"/* eslint-disable @typescript-eslint/no-explicit-any */",
|
|
1813
|
-
""
|
|
1814
|
-
];
|
|
1815
|
-
for (const cls of classes) {
|
|
1816
|
-
const absoluteSourcePath = path2.resolve(baseDir, cls.sourcePath);
|
|
1817
|
-
const importPath = getRelativeImportPath(outputDir, absoluteSourcePath);
|
|
1818
|
-
lines.push(`import { ${cls.name} } from "${importPath}";`);
|
|
1819
|
-
}
|
|
1820
|
-
lines.push("");
|
|
1821
|
-
lines.push("// =============================================================================");
|
|
1822
|
-
lines.push("// Type Metadata Patches");
|
|
1823
|
-
lines.push("// =============================================================================");
|
|
1824
|
-
lines.push("");
|
|
1825
|
-
for (const cls of classes) {
|
|
1826
|
-
const metadataJson = JSON.stringify(cls.typeMetadata, null, 2).split("\n").map((line, i) => i === 0 ? line : " " + line).join("\n");
|
|
1827
|
-
lines.push(`(${cls.name} as any).__formspec_types__ = ${metadataJson};`);
|
|
1828
|
-
lines.push("");
|
|
1829
|
-
}
|
|
1830
|
-
lines.push("// =============================================================================");
|
|
1831
|
-
lines.push("// Inferred Schema Types");
|
|
1832
|
-
lines.push("// =============================================================================");
|
|
1833
|
-
lines.push("");
|
|
1834
|
-
for (const cls of classes) {
|
|
1835
|
-
lines.push(generateSchemaType(cls));
|
|
1836
|
-
lines.push("");
|
|
1837
|
-
}
|
|
1838
|
-
lines.push("// =============================================================================");
|
|
1839
|
-
lines.push("// Type-Safe FormSpec Accessors");
|
|
1840
|
-
lines.push("// =============================================================================");
|
|
1841
|
-
lines.push("");
|
|
1842
|
-
for (const cls of classes) {
|
|
1843
|
-
lines.push(generateTypedAccessor(cls));
|
|
1844
|
-
lines.push("");
|
|
1845
|
-
}
|
|
1846
|
-
return lines.join("\n");
|
|
1847
|
-
}
|
|
1848
|
-
function metadataToTypeString(metadata) {
|
|
1849
|
-
const baseType = metadataToBaseTypeString(metadata);
|
|
1850
|
-
if (metadata.nullable && metadata.optional) {
|
|
1851
|
-
return `${baseType} | null | undefined`;
|
|
1852
|
-
}
|
|
1853
|
-
if (metadata.nullable) {
|
|
1854
|
-
return `${baseType} | null`;
|
|
1855
|
-
}
|
|
1856
|
-
if (metadata.optional) {
|
|
1857
|
-
return `${baseType} | undefined`;
|
|
1858
|
-
}
|
|
1859
|
-
return baseType;
|
|
1860
|
-
}
|
|
1861
|
-
function needsPropertyQuoting(key) {
|
|
1862
|
-
if (!VALID_IDENTIFIER.test(key)) {
|
|
1863
|
-
return true;
|
|
1864
|
-
}
|
|
1865
|
-
return RESERVED_WORDS.has(key);
|
|
1866
|
-
}
|
|
1867
|
-
function escapePropertyKey(key) {
|
|
1868
|
-
if (needsPropertyQuoting(key)) {
|
|
1869
|
-
return JSON.stringify(key);
|
|
1870
|
-
}
|
|
1871
|
-
return key;
|
|
1872
|
-
}
|
|
1873
|
-
function metadataToBaseTypeString(metadata) {
|
|
1874
|
-
switch (metadata.type) {
|
|
1875
|
-
case "string":
|
|
1876
|
-
return "string";
|
|
1877
|
-
case "number":
|
|
1878
|
-
return "number";
|
|
1879
|
-
case "boolean":
|
|
1880
|
-
return "boolean";
|
|
1881
|
-
case "enum":
|
|
1882
|
-
if (metadata.values && metadata.values.length > 0) {
|
|
1883
|
-
return metadata.values.map((v) => typeof v === "string" ? JSON.stringify(v) : String(v)).join(" | ");
|
|
1884
|
-
}
|
|
1885
|
-
return "string";
|
|
1886
|
-
case "array":
|
|
1887
|
-
if (metadata.itemType) {
|
|
1888
|
-
const itemType = metadataToTypeString(metadata.itemType);
|
|
1889
|
-
if (itemType.includes("|")) {
|
|
1890
|
-
return `(${itemType})[]`;
|
|
1891
|
-
}
|
|
1892
|
-
return `${itemType}[]`;
|
|
1893
|
-
}
|
|
1894
|
-
return "unknown[]";
|
|
1895
|
-
case "object":
|
|
1896
|
-
if (metadata.properties && Object.keys(metadata.properties).length > 0) {
|
|
1897
|
-
const props = Object.entries(metadata.properties).map(([key, propMeta]) => {
|
|
1898
|
-
const optional = propMeta.optional ? "?" : "";
|
|
1899
|
-
return `${escapePropertyKey(key)}${optional}: ${metadataToTypeString(propMeta)}`;
|
|
1900
|
-
}).join("; ");
|
|
1901
|
-
return `{ ${props} }`;
|
|
1902
|
-
}
|
|
1903
|
-
return "Record<string, unknown>";
|
|
1904
|
-
default:
|
|
1905
|
-
return "unknown";
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
function generateSchemaType(cls) {
|
|
1909
|
-
const props = Object.entries(cls.typeMetadata).map(([fieldName, metadata]) => {
|
|
1910
|
-
const optional = metadata.optional ? "?" : "";
|
|
1911
|
-
const typeStr = metadataToTypeString(metadata);
|
|
1912
|
-
return ` ${escapePropertyKey(fieldName)}${optional}: ${typeStr};`;
|
|
1913
|
-
}).join("\n");
|
|
1914
|
-
return `export type ${cls.name}Schema = {
|
|
1915
|
-
${props}
|
|
1916
|
-
};`;
|
|
1917
|
-
}
|
|
1918
|
-
function metadataTypeToFieldType(type) {
|
|
1919
|
-
switch (type) {
|
|
1920
|
-
case "string":
|
|
1921
|
-
return "text";
|
|
1922
|
-
case "number":
|
|
1923
|
-
return "number";
|
|
1924
|
-
case "boolean":
|
|
1925
|
-
return "boolean";
|
|
1926
|
-
case "enum":
|
|
1927
|
-
return "enum";
|
|
1928
|
-
case "array":
|
|
1929
|
-
return "array";
|
|
1930
|
-
case "object":
|
|
1931
|
-
return "object";
|
|
1932
|
-
default:
|
|
1933
|
-
return "text";
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
function generateTypedAccessor(cls) {
|
|
1937
|
-
const lines = [];
|
|
1938
|
-
const elementTypes = Object.entries(cls.typeMetadata).map(([fieldName, metadata]) => {
|
|
1939
|
-
const fieldType = metadataTypeToFieldType(metadata.type);
|
|
1940
|
-
const required = !metadata.optional;
|
|
1941
|
-
let elementType = `{ readonly _field: "${fieldType}"; readonly id: "${fieldName}"; readonly required: ${String(required)}`;
|
|
1942
|
-
if (metadata.type === "enum" && metadata.values) {
|
|
1943
|
-
const optionValues = metadata.values.map((v) => typeof v === "string" ? JSON.stringify(v) : String(v)).join(", ");
|
|
1944
|
-
elementType += `; readonly options: readonly [${optionValues}]`;
|
|
1945
|
-
}
|
|
1946
|
-
elementType += " }";
|
|
1947
|
-
return elementType;
|
|
1948
|
-
});
|
|
1949
|
-
lines.push(`export type ${cls.name}Elements = readonly [
|
|
1950
|
-
${elementTypes.join(",\n ")}
|
|
1951
|
-
];`);
|
|
1952
|
-
lines.push("");
|
|
1953
|
-
lines.push(`export type ${cls.name}FormSpec = { readonly elements: ${cls.name}Elements };`);
|
|
1954
|
-
lines.push("");
|
|
1955
|
-
lines.push(`/**`);
|
|
1956
|
-
lines.push(` * Type-safe FormSpec accessor for ${cls.name}.`);
|
|
1957
|
-
lines.push(` * Reads the patched type metadata from the class.`);
|
|
1958
|
-
lines.push(` */`);
|
|
1959
|
-
lines.push(`export function get${cls.name}FormSpec(): ${cls.name}FormSpec {`);
|
|
1960
|
-
lines.push(
|
|
1961
|
-
` const types = (${cls.name} as any).__formspec_types__ as Record<string, unknown>[];`
|
|
1962
|
-
);
|
|
1963
|
-
lines.push(` return { elements: types } as unknown as ${cls.name}FormSpec;`);
|
|
1964
|
-
lines.push(`}`);
|
|
1965
|
-
return lines.join("\n");
|
|
1966
|
-
}
|
|
1967
|
-
function getRelativeImportPath(outputDir, sourcePath) {
|
|
1968
|
-
let relativePath = path2.relative(outputDir, sourcePath);
|
|
1969
|
-
if (!relativePath.startsWith(".")) {
|
|
1970
|
-
relativePath = "./" + relativePath;
|
|
1971
|
-
}
|
|
1972
|
-
relativePath = relativePath.replace(/\\/g, "/");
|
|
1973
|
-
return relativePath + ".js";
|
|
1974
|
-
}
|
|
1975
|
-
function runCodegen(options) {
|
|
1976
|
-
const baseDir = options.baseDir ?? path2.dirname(options.output);
|
|
1977
|
-
const absoluteFiles = options.files.map((f) => path2.resolve(f));
|
|
1978
|
-
console.log(`Scanning ${String(absoluteFiles.length)} file(s) for decorated classes...`);
|
|
1979
|
-
const classes = findDecoratedClasses(absoluteFiles, baseDir);
|
|
1980
|
-
if (classes.length === 0) {
|
|
1981
|
-
console.log("No decorated classes found.");
|
|
1982
|
-
return;
|
|
1983
|
-
}
|
|
1984
|
-
console.log(`Found ${String(classes.length)} decorated class(es):`);
|
|
1985
|
-
for (const cls of classes) {
|
|
1986
|
-
const fieldCount = Object.keys(cls.typeMetadata).length;
|
|
1987
|
-
console.log(` - ${cls.name} (${String(fieldCount)} field(s))`);
|
|
1988
|
-
}
|
|
1989
|
-
const unexported = classes.filter((cls) => !cls.isExported);
|
|
1990
|
-
if (unexported.length > 0) {
|
|
1991
|
-
console.warn(
|
|
1992
|
-
`
|
|
1993
|
-
\u26A0\uFE0F Warning: The following decorated classes are not exported from their source files:`
|
|
1994
|
-
);
|
|
1995
|
-
for (const cls of unexported) {
|
|
1996
|
-
console.warn(` - ${cls.name} (${cls.sourcePath})`);
|
|
1997
|
-
}
|
|
1998
|
-
console.warn(
|
|
1999
|
-
`
|
|
2000
|
-
The generated code will fail to compile because it cannot import these classes.`
|
|
2001
|
-
);
|
|
2002
|
-
console.warn(` To fix this, add 'export' to the class declaration, for example:`);
|
|
2003
|
-
console.warn(` export class YourClassName { ... }
|
|
2004
|
-
`);
|
|
2005
|
-
}
|
|
2006
|
-
const output = generateCodegenOutput(classes, options.output, baseDir);
|
|
2007
|
-
const outputDir = path2.dirname(options.output);
|
|
2008
|
-
if (!fs.existsSync(outputDir)) {
|
|
2009
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
2010
|
-
}
|
|
2011
|
-
fs.writeFileSync(options.output, output);
|
|
2012
|
-
console.log(`
|
|
2013
|
-
Generated: ${options.output}`);
|
|
2014
|
-
}
|
|
2015
|
-
var ts6, path2, fs, VALID_IDENTIFIER, RESERVED_WORDS;
|
|
2016
|
-
var init_codegen = __esm({
|
|
2017
|
-
"src/codegen/index.ts"() {
|
|
2018
|
-
"use strict";
|
|
2019
|
-
ts6 = __toESM(require("typescript"), 1);
|
|
2020
|
-
path2 = __toESM(require("path"), 1);
|
|
2021
|
-
fs = __toESM(require("fs"), 1);
|
|
2022
|
-
VALID_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
2023
|
-
RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
2024
|
-
// Keywords
|
|
2025
|
-
"break",
|
|
2026
|
-
"case",
|
|
2027
|
-
"catch",
|
|
2028
|
-
"continue",
|
|
2029
|
-
"debugger",
|
|
2030
|
-
"default",
|
|
2031
|
-
"delete",
|
|
2032
|
-
"do",
|
|
2033
|
-
"else",
|
|
2034
|
-
"finally",
|
|
2035
|
-
"for",
|
|
2036
|
-
"function",
|
|
2037
|
-
"if",
|
|
2038
|
-
"in",
|
|
2039
|
-
"instanceof",
|
|
2040
|
-
"new",
|
|
2041
|
-
"return",
|
|
2042
|
-
"switch",
|
|
2043
|
-
"this",
|
|
2044
|
-
"throw",
|
|
2045
|
-
"try",
|
|
2046
|
-
"typeof",
|
|
2047
|
-
"var",
|
|
2048
|
-
"void",
|
|
2049
|
-
"while",
|
|
2050
|
-
"with",
|
|
2051
|
-
"class",
|
|
2052
|
-
"const",
|
|
2053
|
-
"enum",
|
|
2054
|
-
"export",
|
|
2055
|
-
"extends",
|
|
2056
|
-
"import",
|
|
2057
|
-
"super",
|
|
2058
|
-
"implements",
|
|
2059
|
-
"interface",
|
|
2060
|
-
"let",
|
|
2061
|
-
"package",
|
|
2062
|
-
"private",
|
|
2063
|
-
"protected",
|
|
2064
|
-
"public",
|
|
2065
|
-
"static",
|
|
2066
|
-
"yield",
|
|
2067
|
-
// ES6+ keywords
|
|
2068
|
-
"async",
|
|
2069
|
-
"await",
|
|
2070
|
-
// Literal values (not technically keywords but best to quote)
|
|
2071
|
-
"null",
|
|
2072
|
-
"true",
|
|
2073
|
-
"false",
|
|
2074
|
-
// Accessor keywords
|
|
2075
|
-
"get",
|
|
2076
|
-
"set",
|
|
2077
|
-
// Strict mode reserved
|
|
2078
|
-
"arguments",
|
|
2079
|
-
"eval"
|
|
2080
|
-
]);
|
|
1844
|
+
init_canonicalize();
|
|
1845
|
+
init_ir_generator();
|
|
1846
|
+
init_ir_generator2();
|
|
2081
1847
|
}
|
|
2082
1848
|
});
|
|
2083
1849
|
|
|
@@ -2088,13 +1854,10 @@ __export(index_exports, {
|
|
|
2088
1854
|
categorizationSchema: () => categorizationSchema,
|
|
2089
1855
|
categorySchema: () => categorySchema,
|
|
2090
1856
|
controlSchema: () => controlSchema,
|
|
2091
|
-
findDecoratedClasses: () => findDecoratedClasses,
|
|
2092
|
-
generateCodegenOutput: () => generateCodegenOutput,
|
|
2093
1857
|
generateJsonSchema: () => generateJsonSchema,
|
|
2094
1858
|
generateSchemas: () => generateSchemas,
|
|
2095
1859
|
generateSchemasFromClass: () => generateSchemasFromClass,
|
|
2096
1860
|
generateUiSchema: () => generateUiSchema,
|
|
2097
|
-
generateUiSchemaFromFields: () => generateUiSchemaFromFields,
|
|
2098
1861
|
getSchemaExtension: () => getSchemaExtension,
|
|
2099
1862
|
groupLayoutSchema: () => groupLayoutSchema,
|
|
2100
1863
|
horizontalLayoutSchema: () => horizontalLayoutSchema,
|
|
@@ -2104,7 +1867,6 @@ __export(index_exports, {
|
|
|
2104
1867
|
ruleConditionSchema: () => ruleConditionSchema,
|
|
2105
1868
|
ruleEffectSchema: () => ruleEffectSchema,
|
|
2106
1869
|
ruleSchema: () => ruleSchema,
|
|
2107
|
-
runCodegen: () => runCodegen,
|
|
2108
1870
|
schemaBasedConditionSchema: () => schemaBasedConditionSchema,
|
|
2109
1871
|
setSchemaExtension: () => setSchemaExtension,
|
|
2110
1872
|
uiSchemaElementSchema: () => uiSchemaElementSchema,
|
|
@@ -2122,35 +1884,34 @@ function buildFormSchemas(form) {
|
|
|
2122
1884
|
function writeSchemas(form, options) {
|
|
2123
1885
|
const { outDir, name = "schema", indent = 2 } = options;
|
|
2124
1886
|
const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
|
|
2125
|
-
if (!
|
|
2126
|
-
|
|
1887
|
+
if (!fs.existsSync(outDir)) {
|
|
1888
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
2127
1889
|
}
|
|
2128
|
-
const jsonSchemaPath =
|
|
2129
|
-
const uiSchemaPath =
|
|
2130
|
-
|
|
2131
|
-
|
|
1890
|
+
const jsonSchemaPath = path2.join(outDir, `${name}-schema.json`);
|
|
1891
|
+
const uiSchemaPath = path2.join(outDir, `${name}-uischema.json`);
|
|
1892
|
+
fs.writeFileSync(jsonSchemaPath, JSON.stringify(jsonSchema, null, indent));
|
|
1893
|
+
fs.writeFileSync(uiSchemaPath, JSON.stringify(uiSchema2, null, indent));
|
|
2132
1894
|
return { jsonSchemaPath, uiSchemaPath };
|
|
2133
1895
|
}
|
|
2134
|
-
var
|
|
1896
|
+
var fs, path2;
|
|
2135
1897
|
var init_index = __esm({
|
|
2136
1898
|
"src/index.ts"() {
|
|
2137
1899
|
"use strict";
|
|
2138
1900
|
init_generator();
|
|
2139
1901
|
init_generator2();
|
|
2140
|
-
|
|
2141
|
-
|
|
1902
|
+
fs = __toESM(require("fs"), 1);
|
|
1903
|
+
path2 = __toESM(require("path"), 1);
|
|
2142
1904
|
init_types();
|
|
2143
|
-
init_schema2();
|
|
2144
1905
|
init_schema();
|
|
1906
|
+
init_schema2();
|
|
2145
1907
|
init_generator();
|
|
2146
1908
|
init_generator2();
|
|
2147
1909
|
init_class_schema();
|
|
2148
|
-
init_codegen();
|
|
2149
1910
|
}
|
|
2150
1911
|
});
|
|
2151
1912
|
|
|
2152
1913
|
// src/cli.ts
|
|
2153
|
-
var
|
|
1914
|
+
var path3 = __toESM(require("path"), 1);
|
|
2154
1915
|
var import_node_url = require("url");
|
|
2155
1916
|
function printHelp() {
|
|
2156
1917
|
console.log(`
|
|
@@ -2222,7 +1983,7 @@ function parseArgs(args) {
|
|
|
2222
1983
|
return null;
|
|
2223
1984
|
}
|
|
2224
1985
|
if (!name) {
|
|
2225
|
-
name =
|
|
1986
|
+
name = path3.basename(inputFile, path3.extname(inputFile));
|
|
2226
1987
|
}
|
|
2227
1988
|
return { inputFile, outDir, name };
|
|
2228
1989
|
}
|
|
@@ -2233,7 +1994,7 @@ async function main() {
|
|
|
2233
1994
|
process.exit(1);
|
|
2234
1995
|
}
|
|
2235
1996
|
const { inputFile, outDir, name } = options;
|
|
2236
|
-
const absoluteInput =
|
|
1997
|
+
const absoluteInput = path3.resolve(process.cwd(), inputFile);
|
|
2237
1998
|
try {
|
|
2238
1999
|
const fileUrl = (0, import_node_url.pathToFileURL)(absoluteInput).href;
|
|
2239
2000
|
const module2 = await import(fileUrl);
|