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