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