@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/internals.cjs
CHANGED
|
@@ -30,20 +30,372 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/internals.ts
|
|
31
31
|
var internals_exports = {};
|
|
32
32
|
__export(internals_exports, {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
analyzeClassToIR: () => analyzeClassToIR,
|
|
34
|
+
analyzeInterfaceToIR: () => analyzeInterfaceToIR,
|
|
35
|
+
analyzeTypeAliasToIR: () => analyzeTypeAliasToIR,
|
|
36
|
+
canonicalizeChainDSL: () => canonicalizeChainDSL,
|
|
37
|
+
canonicalizeTSDoc: () => canonicalizeTSDoc,
|
|
36
38
|
collectFormSpecReferences: () => collectFormSpecReferences,
|
|
39
|
+
createExtensionRegistry: () => createExtensionRegistry,
|
|
37
40
|
createProgramContext: () => createProgramContext,
|
|
38
41
|
findClassByName: () => findClassByName,
|
|
39
42
|
findInterfaceByName: () => findInterfaceByName,
|
|
40
43
|
findTypeAliasByName: () => findTypeAliasByName,
|
|
41
44
|
generateClassSchemas: () => generateClassSchemas,
|
|
45
|
+
generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
|
|
42
46
|
generateMethodSchemas: () => generateMethodSchemas,
|
|
43
|
-
|
|
47
|
+
generateUiSchemaFromIR: () => generateUiSchemaFromIR,
|
|
48
|
+
validateIR: () => validateIR
|
|
44
49
|
});
|
|
45
50
|
module.exports = __toCommonJS(internals_exports);
|
|
46
51
|
|
|
52
|
+
// src/canonicalize/chain-dsl-canonicalizer.ts
|
|
53
|
+
var import_core = require("@formspec/core");
|
|
54
|
+
var CHAIN_DSL_PROVENANCE = {
|
|
55
|
+
surface: "chain-dsl",
|
|
56
|
+
file: "",
|
|
57
|
+
line: 0,
|
|
58
|
+
column: 0
|
|
59
|
+
};
|
|
60
|
+
function isGroup(el) {
|
|
61
|
+
return el._type === "group";
|
|
62
|
+
}
|
|
63
|
+
function isConditional(el) {
|
|
64
|
+
return el._type === "conditional";
|
|
65
|
+
}
|
|
66
|
+
function isField(el) {
|
|
67
|
+
return el._type === "field";
|
|
68
|
+
}
|
|
69
|
+
function canonicalizeChainDSL(form) {
|
|
70
|
+
return {
|
|
71
|
+
kind: "form-ir",
|
|
72
|
+
irVersion: import_core.IR_VERSION,
|
|
73
|
+
elements: canonicalizeElements(form.elements),
|
|
74
|
+
typeRegistry: {},
|
|
75
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function canonicalizeElements(elements) {
|
|
79
|
+
return elements.map(canonicalizeElement);
|
|
80
|
+
}
|
|
81
|
+
function canonicalizeElement(element) {
|
|
82
|
+
if (isField(element)) {
|
|
83
|
+
return canonicalizeField(element);
|
|
84
|
+
}
|
|
85
|
+
if (isGroup(element)) {
|
|
86
|
+
return canonicalizeGroup(element);
|
|
87
|
+
}
|
|
88
|
+
if (isConditional(element)) {
|
|
89
|
+
return canonicalizeConditional(element);
|
|
90
|
+
}
|
|
91
|
+
const _exhaustive = element;
|
|
92
|
+
throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
|
|
93
|
+
}
|
|
94
|
+
function canonicalizeField(field) {
|
|
95
|
+
switch (field._field) {
|
|
96
|
+
case "text":
|
|
97
|
+
return canonicalizeTextField(field);
|
|
98
|
+
case "number":
|
|
99
|
+
return canonicalizeNumberField(field);
|
|
100
|
+
case "boolean":
|
|
101
|
+
return canonicalizeBooleanField(field);
|
|
102
|
+
case "enum":
|
|
103
|
+
return canonicalizeStaticEnumField(field);
|
|
104
|
+
case "dynamic_enum":
|
|
105
|
+
return canonicalizeDynamicEnumField(field);
|
|
106
|
+
case "dynamic_schema":
|
|
107
|
+
return canonicalizeDynamicSchemaField(field);
|
|
108
|
+
case "array":
|
|
109
|
+
return canonicalizeArrayField(field);
|
|
110
|
+
case "object":
|
|
111
|
+
return canonicalizeObjectField(field);
|
|
112
|
+
default: {
|
|
113
|
+
const _exhaustive = field;
|
|
114
|
+
throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function canonicalizeTextField(field) {
|
|
119
|
+
const type = { kind: "primitive", primitiveKind: "string" };
|
|
120
|
+
return buildFieldNode(
|
|
121
|
+
field.name,
|
|
122
|
+
type,
|
|
123
|
+
field.required,
|
|
124
|
+
buildAnnotations(field.label, field.placeholder)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
function canonicalizeNumberField(field) {
|
|
128
|
+
const type = { kind: "primitive", primitiveKind: "number" };
|
|
129
|
+
const constraints = [];
|
|
130
|
+
if (field.min !== void 0) {
|
|
131
|
+
const c = {
|
|
132
|
+
kind: "constraint",
|
|
133
|
+
constraintKind: "minimum",
|
|
134
|
+
value: field.min,
|
|
135
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
136
|
+
};
|
|
137
|
+
constraints.push(c);
|
|
138
|
+
}
|
|
139
|
+
if (field.max !== void 0) {
|
|
140
|
+
const c = {
|
|
141
|
+
kind: "constraint",
|
|
142
|
+
constraintKind: "maximum",
|
|
143
|
+
value: field.max,
|
|
144
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
145
|
+
};
|
|
146
|
+
constraints.push(c);
|
|
147
|
+
}
|
|
148
|
+
return buildFieldNode(
|
|
149
|
+
field.name,
|
|
150
|
+
type,
|
|
151
|
+
field.required,
|
|
152
|
+
buildAnnotations(field.label),
|
|
153
|
+
constraints
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
function canonicalizeBooleanField(field) {
|
|
157
|
+
const type = { kind: "primitive", primitiveKind: "boolean" };
|
|
158
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
159
|
+
}
|
|
160
|
+
function canonicalizeStaticEnumField(field) {
|
|
161
|
+
const members = field.options.map((opt) => {
|
|
162
|
+
if (typeof opt === "string") {
|
|
163
|
+
return { value: opt };
|
|
164
|
+
}
|
|
165
|
+
return { value: opt.id, displayName: opt.label };
|
|
166
|
+
});
|
|
167
|
+
const type = { kind: "enum", members };
|
|
168
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
169
|
+
}
|
|
170
|
+
function canonicalizeDynamicEnumField(field) {
|
|
171
|
+
const type = {
|
|
172
|
+
kind: "dynamic",
|
|
173
|
+
dynamicKind: "enum",
|
|
174
|
+
sourceKey: field.source,
|
|
175
|
+
parameterFields: field.params ? [...field.params] : []
|
|
176
|
+
};
|
|
177
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
178
|
+
}
|
|
179
|
+
function canonicalizeDynamicSchemaField(field) {
|
|
180
|
+
const type = {
|
|
181
|
+
kind: "dynamic",
|
|
182
|
+
dynamicKind: "schema",
|
|
183
|
+
sourceKey: field.schemaSource,
|
|
184
|
+
parameterFields: []
|
|
185
|
+
};
|
|
186
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
187
|
+
}
|
|
188
|
+
function canonicalizeArrayField(field) {
|
|
189
|
+
const itemProperties = buildObjectProperties(field.items);
|
|
190
|
+
const itemsType = {
|
|
191
|
+
kind: "object",
|
|
192
|
+
properties: itemProperties,
|
|
193
|
+
additionalProperties: false
|
|
194
|
+
};
|
|
195
|
+
const type = { kind: "array", items: itemsType };
|
|
196
|
+
const constraints = [];
|
|
197
|
+
if (field.minItems !== void 0) {
|
|
198
|
+
const c = {
|
|
199
|
+
kind: "constraint",
|
|
200
|
+
constraintKind: "minItems",
|
|
201
|
+
value: field.minItems,
|
|
202
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
203
|
+
};
|
|
204
|
+
constraints.push(c);
|
|
205
|
+
}
|
|
206
|
+
if (field.maxItems !== void 0) {
|
|
207
|
+
const c = {
|
|
208
|
+
kind: "constraint",
|
|
209
|
+
constraintKind: "maxItems",
|
|
210
|
+
value: field.maxItems,
|
|
211
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
212
|
+
};
|
|
213
|
+
constraints.push(c);
|
|
214
|
+
}
|
|
215
|
+
return buildFieldNode(
|
|
216
|
+
field.name,
|
|
217
|
+
type,
|
|
218
|
+
field.required,
|
|
219
|
+
buildAnnotations(field.label),
|
|
220
|
+
constraints
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
function canonicalizeObjectField(field) {
|
|
224
|
+
const properties = buildObjectProperties(field.properties);
|
|
225
|
+
const type = {
|
|
226
|
+
kind: "object",
|
|
227
|
+
properties,
|
|
228
|
+
additionalProperties: false
|
|
229
|
+
};
|
|
230
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
231
|
+
}
|
|
232
|
+
function canonicalizeGroup(g) {
|
|
233
|
+
return {
|
|
234
|
+
kind: "group",
|
|
235
|
+
label: g.label,
|
|
236
|
+
elements: canonicalizeElements(g.elements),
|
|
237
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function canonicalizeConditional(c) {
|
|
241
|
+
return {
|
|
242
|
+
kind: "conditional",
|
|
243
|
+
fieldName: c.field,
|
|
244
|
+
// Conditional values from the chain DSL are JSON-serializable primitives
|
|
245
|
+
// (strings, numbers, booleans) produced by the `is()` predicate helper.
|
|
246
|
+
value: assertJsonValue(c.value),
|
|
247
|
+
elements: canonicalizeElements(c.elements),
|
|
248
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function assertJsonValue(v) {
|
|
252
|
+
if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
253
|
+
return v;
|
|
254
|
+
}
|
|
255
|
+
if (Array.isArray(v)) {
|
|
256
|
+
return v.map(assertJsonValue);
|
|
257
|
+
}
|
|
258
|
+
if (typeof v === "object") {
|
|
259
|
+
const result = {};
|
|
260
|
+
for (const [key, val] of Object.entries(v)) {
|
|
261
|
+
result[key] = assertJsonValue(val);
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
|
|
266
|
+
}
|
|
267
|
+
function buildFieldNode(name, type, required, annotations, constraints = []) {
|
|
268
|
+
return {
|
|
269
|
+
kind: "field",
|
|
270
|
+
name,
|
|
271
|
+
type,
|
|
272
|
+
required: required === true,
|
|
273
|
+
constraints,
|
|
274
|
+
annotations,
|
|
275
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function buildAnnotations(label, placeholder) {
|
|
279
|
+
const annotations = [];
|
|
280
|
+
if (label !== void 0) {
|
|
281
|
+
const a = {
|
|
282
|
+
kind: "annotation",
|
|
283
|
+
annotationKind: "displayName",
|
|
284
|
+
value: label,
|
|
285
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
286
|
+
};
|
|
287
|
+
annotations.push(a);
|
|
288
|
+
}
|
|
289
|
+
if (placeholder !== void 0) {
|
|
290
|
+
const a = {
|
|
291
|
+
kind: "annotation",
|
|
292
|
+
annotationKind: "placeholder",
|
|
293
|
+
value: placeholder,
|
|
294
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
295
|
+
};
|
|
296
|
+
annotations.push(a);
|
|
297
|
+
}
|
|
298
|
+
return annotations;
|
|
299
|
+
}
|
|
300
|
+
function buildObjectProperties(elements, insideConditional = false) {
|
|
301
|
+
const properties = [];
|
|
302
|
+
for (const el of elements) {
|
|
303
|
+
if (isField(el)) {
|
|
304
|
+
const fieldNode = canonicalizeField(el);
|
|
305
|
+
properties.push({
|
|
306
|
+
name: fieldNode.name,
|
|
307
|
+
type: fieldNode.type,
|
|
308
|
+
// Fields inside a conditional branch are always optional in the
|
|
309
|
+
// data schema, regardless of their `required` flag — the condition
|
|
310
|
+
// may not be met, so the field may be absent.
|
|
311
|
+
optional: insideConditional || !fieldNode.required,
|
|
312
|
+
constraints: fieldNode.constraints,
|
|
313
|
+
annotations: fieldNode.annotations,
|
|
314
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
315
|
+
});
|
|
316
|
+
} else if (isGroup(el)) {
|
|
317
|
+
properties.push(...buildObjectProperties(el.elements, insideConditional));
|
|
318
|
+
} else if (isConditional(el)) {
|
|
319
|
+
properties.push(...buildObjectProperties(el.elements, true));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return properties;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/canonicalize/tsdoc-canonicalizer.ts
|
|
326
|
+
var import_core2 = require("@formspec/core");
|
|
327
|
+
function canonicalizeTSDoc(analysis, source) {
|
|
328
|
+
const file = source?.file ?? "";
|
|
329
|
+
const provenance = {
|
|
330
|
+
surface: "tsdoc",
|
|
331
|
+
file,
|
|
332
|
+
line: 1,
|
|
333
|
+
column: 0
|
|
334
|
+
};
|
|
335
|
+
const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
|
|
336
|
+
return {
|
|
337
|
+
kind: "form-ir",
|
|
338
|
+
irVersion: import_core2.IR_VERSION,
|
|
339
|
+
elements,
|
|
340
|
+
typeRegistry: analysis.typeRegistry,
|
|
341
|
+
provenance
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function assembleElements(fields, layouts, provenance) {
|
|
345
|
+
const elements = [];
|
|
346
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
347
|
+
const topLevelOrder = [];
|
|
348
|
+
for (let i = 0; i < fields.length; i++) {
|
|
349
|
+
const field = fields[i];
|
|
350
|
+
const layout = layouts[i];
|
|
351
|
+
if (!field || !layout) continue;
|
|
352
|
+
const element = wrapInConditional(field, layout, provenance);
|
|
353
|
+
if (layout.groupLabel !== void 0) {
|
|
354
|
+
const label = layout.groupLabel;
|
|
355
|
+
let groupElements = groupMap.get(label);
|
|
356
|
+
if (!groupElements) {
|
|
357
|
+
groupElements = [];
|
|
358
|
+
groupMap.set(label, groupElements);
|
|
359
|
+
topLevelOrder.push({ type: "group", label });
|
|
360
|
+
}
|
|
361
|
+
groupElements.push(element);
|
|
362
|
+
} else {
|
|
363
|
+
topLevelOrder.push({ type: "element", element });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const entry of topLevelOrder) {
|
|
367
|
+
if (entry.type === "group") {
|
|
368
|
+
const groupElements = groupMap.get(entry.label);
|
|
369
|
+
if (groupElements) {
|
|
370
|
+
const groupNode = {
|
|
371
|
+
kind: "group",
|
|
372
|
+
label: entry.label,
|
|
373
|
+
elements: groupElements,
|
|
374
|
+
provenance
|
|
375
|
+
};
|
|
376
|
+
elements.push(groupNode);
|
|
377
|
+
groupMap.delete(entry.label);
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
elements.push(entry.element);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return elements;
|
|
384
|
+
}
|
|
385
|
+
function wrapInConditional(field, layout, provenance) {
|
|
386
|
+
if (layout.showWhen === void 0) {
|
|
387
|
+
return field;
|
|
388
|
+
}
|
|
389
|
+
const conditional = {
|
|
390
|
+
kind: "conditional",
|
|
391
|
+
fieldName: layout.showWhen.field,
|
|
392
|
+
value: layout.showWhen.value,
|
|
393
|
+
elements: [field],
|
|
394
|
+
provenance
|
|
395
|
+
};
|
|
396
|
+
return conditional;
|
|
397
|
+
}
|
|
398
|
+
|
|
47
399
|
// src/analyzer/program.ts
|
|
48
400
|
var ts = __toESM(require("typescript"), 1);
|
|
49
401
|
var path = __toESM(require("path"), 1);
|
|
@@ -119,290 +471,104 @@ function findTypeAliasByName(sourceFile, aliasName) {
|
|
|
119
471
|
// src/analyzer/class-analyzer.ts
|
|
120
472
|
var ts4 = __toESM(require("typescript"), 1);
|
|
121
473
|
|
|
122
|
-
// src/analyzer/
|
|
474
|
+
// src/analyzer/jsdoc-constraints.ts
|
|
475
|
+
var ts3 = __toESM(require("typescript"), 1);
|
|
476
|
+
var import_core4 = require("@formspec/core");
|
|
477
|
+
|
|
478
|
+
// src/analyzer/tsdoc-parser.ts
|
|
123
479
|
var ts2 = __toESM(require("typescript"), 1);
|
|
124
|
-
var
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
480
|
+
var import_tsdoc = require("@microsoft/tsdoc");
|
|
481
|
+
var import_core3 = require("@formspec/core");
|
|
482
|
+
var NUMERIC_CONSTRAINT_MAP = {
|
|
483
|
+
Minimum: "minimum",
|
|
484
|
+
Maximum: "maximum",
|
|
485
|
+
ExclusiveMinimum: "exclusiveMinimum",
|
|
486
|
+
ExclusiveMaximum: "exclusiveMaximum"
|
|
487
|
+
};
|
|
488
|
+
var LENGTH_CONSTRAINT_MAP = {
|
|
489
|
+
MinLength: "minLength",
|
|
490
|
+
MaxLength: "maxLength"
|
|
491
|
+
};
|
|
492
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
|
|
493
|
+
function isBuiltinConstraintName(tagName) {
|
|
494
|
+
return tagName in import_core3.BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
136
495
|
}
|
|
137
|
-
function
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const callee = expr.expression;
|
|
148
|
-
let name = null;
|
|
149
|
-
if (ts2.isIdentifier(callee)) {
|
|
150
|
-
name = callee.text;
|
|
151
|
-
} else if (ts2.isPropertyAccessExpression(callee)) {
|
|
152
|
-
name = callee.name.text;
|
|
153
|
-
}
|
|
154
|
-
if (!name) return null;
|
|
155
|
-
const args = expr.arguments.map(extractArgValue);
|
|
156
|
-
return {
|
|
157
|
-
name,
|
|
158
|
-
args,
|
|
159
|
-
node: decorator
|
|
160
|
-
};
|
|
496
|
+
function createFormSpecTSDocConfig() {
|
|
497
|
+
const config = new import_tsdoc.TSDocConfiguration();
|
|
498
|
+
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
499
|
+
config.addTagDefinition(
|
|
500
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
501
|
+
tagName: "@" + tagName,
|
|
502
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
503
|
+
allowMultiple: true
|
|
504
|
+
})
|
|
505
|
+
);
|
|
161
506
|
}
|
|
162
|
-
return
|
|
507
|
+
return config;
|
|
163
508
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
if (ts2.isPrefixUnaryExpression(node)) {
|
|
181
|
-
if (node.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(node.operand)) {
|
|
182
|
-
return -Number(node.operand.text);
|
|
183
|
-
}
|
|
184
|
-
if (node.operator === ts2.SyntaxKind.PlusToken && ts2.isNumericLiteral(node.operand)) {
|
|
185
|
-
return Number(node.operand.text);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (ts2.isArrayLiteralExpression(node)) {
|
|
189
|
-
return node.elements.map((el) => {
|
|
190
|
-
if (ts2.isSpreadElement(el)) {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
return extractArgValue(el);
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
if (ts2.isObjectLiteralExpression(node)) {
|
|
197
|
-
const obj = {};
|
|
198
|
-
for (const prop of node.properties) {
|
|
199
|
-
if (ts2.isPropertyAssignment(prop)) {
|
|
200
|
-
const key = getPropertyName(prop.name);
|
|
201
|
-
if (key) {
|
|
202
|
-
obj[key] = extractArgValue(prop.initializer);
|
|
203
|
-
}
|
|
204
|
-
} else if (ts2.isShorthandPropertyAssignment(prop)) {
|
|
205
|
-
const key = prop.name.text;
|
|
206
|
-
obj[key] = null;
|
|
509
|
+
var sharedParser;
|
|
510
|
+
function getParser() {
|
|
511
|
+
sharedParser ??= new import_tsdoc.TSDocParser(createFormSpecTSDocConfig());
|
|
512
|
+
return sharedParser;
|
|
513
|
+
}
|
|
514
|
+
function parseTSDocTags(node, file = "") {
|
|
515
|
+
const constraints = [];
|
|
516
|
+
const annotations = [];
|
|
517
|
+
const sourceFile = node.getSourceFile();
|
|
518
|
+
const sourceText = sourceFile.getFullText();
|
|
519
|
+
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
520
|
+
if (commentRanges) {
|
|
521
|
+
for (const range of commentRanges) {
|
|
522
|
+
if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
|
|
523
|
+
continue;
|
|
207
524
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (ts2.isNoSubstitutionTemplateLiteral(node)) {
|
|
212
|
-
return node.text;
|
|
213
|
-
}
|
|
214
|
-
if (ts2.isRegularExpressionLiteral(node)) {
|
|
215
|
-
const regexText = node.text;
|
|
216
|
-
const lastSlash = regexText.lastIndexOf("/");
|
|
217
|
-
if (lastSlash > 0) {
|
|
218
|
-
return regexText.substring(1, lastSlash);
|
|
219
|
-
}
|
|
220
|
-
return regexText;
|
|
221
|
-
}
|
|
222
|
-
if (ts2.isNewExpression(node)) {
|
|
223
|
-
if (ts2.isIdentifier(node.expression) && node.expression.text === "RegExp" && node.arguments && node.arguments.length > 0) {
|
|
224
|
-
const firstArg = node.arguments[0];
|
|
225
|
-
if (firstArg && ts2.isStringLiteral(firstArg)) {
|
|
226
|
-
return firstArg.text;
|
|
525
|
+
const commentText = sourceText.substring(range.pos, range.end);
|
|
526
|
+
if (!commentText.startsWith("/**")) {
|
|
527
|
+
continue;
|
|
227
528
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (ts2.isNumericLiteral(name)) {
|
|
243
|
-
return name.text;
|
|
244
|
-
}
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
var FORMSPEC_DECORATORS = {
|
|
248
|
-
// Display metadata
|
|
249
|
-
Field: { argTypes: ["object"] },
|
|
250
|
-
// Grouping
|
|
251
|
-
Group: { argTypes: ["string"] },
|
|
252
|
-
// Conditional display
|
|
253
|
-
ShowWhen: { argTypes: ["object"] },
|
|
254
|
-
// Enum options
|
|
255
|
-
EnumOptions: { argTypes: ["array"] },
|
|
256
|
-
// Numeric constraints
|
|
257
|
-
Minimum: { argTypes: ["number"] },
|
|
258
|
-
Maximum: { argTypes: ["number"] },
|
|
259
|
-
ExclusiveMinimum: { argTypes: ["number"] },
|
|
260
|
-
ExclusiveMaximum: { argTypes: ["number"] },
|
|
261
|
-
// String constraints
|
|
262
|
-
MinLength: { argTypes: ["number"] },
|
|
263
|
-
MaxLength: { argTypes: ["number"] },
|
|
264
|
-
Pattern: { argTypes: ["string"] }
|
|
265
|
-
};
|
|
266
|
-
function isFormSpecDecoratorsPath(fileName) {
|
|
267
|
-
const normalized = fileName.replace(/\\/g, "/");
|
|
268
|
-
return normalized.includes("node_modules/@formspec/decorators") || normalized.includes("/packages/decorators/");
|
|
269
|
-
}
|
|
270
|
-
function resolveDecorator(decorator, checker) {
|
|
271
|
-
const expr = decorator.expression;
|
|
272
|
-
let targetNode;
|
|
273
|
-
let name;
|
|
274
|
-
if (ts2.isIdentifier(expr)) {
|
|
275
|
-
targetNode = expr;
|
|
276
|
-
name = expr.text;
|
|
277
|
-
} else if (ts2.isCallExpression(expr)) {
|
|
278
|
-
if (ts2.isIdentifier(expr.expression)) {
|
|
279
|
-
targetNode = expr.expression;
|
|
280
|
-
name = expr.expression.text;
|
|
281
|
-
} else {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
} else {
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
if (name in FORMSPEC_DECORATORS) {
|
|
288
|
-
const symbol = checker.getSymbolAtLocation(targetNode);
|
|
289
|
-
if (symbol) {
|
|
290
|
-
const declarations = symbol.declarations;
|
|
291
|
-
if (declarations && declarations.length > 0) {
|
|
292
|
-
const decl = declarations[0];
|
|
293
|
-
if (decl) {
|
|
294
|
-
const sourceFile = decl.getSourceFile();
|
|
295
|
-
const fileName = sourceFile.fileName;
|
|
296
|
-
if (isFormSpecDecoratorsPath(fileName)) {
|
|
297
|
-
return {
|
|
298
|
-
name,
|
|
299
|
-
isFormSpec: true,
|
|
300
|
-
isMarker: !ts2.isCallExpression(expr)
|
|
301
|
-
};
|
|
302
|
-
}
|
|
529
|
+
const parser = getParser();
|
|
530
|
+
const parserContext = parser.parseRange(
|
|
531
|
+
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
532
|
+
);
|
|
533
|
+
const docComment = parserContext.docComment;
|
|
534
|
+
for (const block of docComment.customBlocks) {
|
|
535
|
+
const tagName = block.blockTag.tagName.substring(1);
|
|
536
|
+
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
537
|
+
const text = extractBlockText(block).trim();
|
|
538
|
+
if (text === "") continue;
|
|
539
|
+
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
540
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
541
|
+
if (constraintNode) {
|
|
542
|
+
constraints.push(constraintNode);
|
|
303
543
|
}
|
|
304
544
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
let extendsBuiltin;
|
|
312
|
-
let extensionName;
|
|
313
|
-
let isMarker = false;
|
|
314
|
-
for (const prop of props) {
|
|
315
|
-
const escapedName = prop.getEscapedName();
|
|
316
|
-
if (escapedName.startsWith("__@") && (escapedName.includes("formspec.extends") || escapedName.includes("FORMSPEC_EXTENDS"))) {
|
|
317
|
-
const propType = checker.getTypeOfSymbol(prop);
|
|
318
|
-
if (propType.isStringLiteral()) {
|
|
319
|
-
extendsBuiltin = propType.value;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
if (escapedName.startsWith("__@") && (escapedName.includes("formspec.extension") || escapedName.includes("FORMSPEC_EXTENSION"))) {
|
|
323
|
-
const propType = checker.getTypeOfSymbol(prop);
|
|
324
|
-
if (propType.isStringLiteral()) {
|
|
325
|
-
extensionName = propType.value;
|
|
545
|
+
if (docComment.deprecatedBlock !== void 0) {
|
|
546
|
+
annotations.push({
|
|
547
|
+
kind: "annotation",
|
|
548
|
+
annotationKind: "deprecated",
|
|
549
|
+
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
550
|
+
});
|
|
326
551
|
}
|
|
327
552
|
}
|
|
328
|
-
if (escapedName.startsWith("__@") && (escapedName.includes("formspec.marker") || escapedName.includes("FORMSPEC_MARKER"))) {
|
|
329
|
-
isMarker = true;
|
|
330
|
-
}
|
|
331
553
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
name,
|
|
335
|
-
extendsBuiltin,
|
|
336
|
-
isFormSpec: true,
|
|
337
|
-
isMarker: false
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
if (extensionName) {
|
|
341
|
-
return {
|
|
342
|
-
name,
|
|
343
|
-
extensionName,
|
|
344
|
-
isFormSpec: true,
|
|
345
|
-
isMarker
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
if (isMarker) {
|
|
349
|
-
return {
|
|
350
|
-
name,
|
|
351
|
-
isFormSpec: true,
|
|
352
|
-
isMarker: true
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// src/analyzer/jsdoc-constraints.ts
|
|
359
|
-
var ts3 = __toESM(require("typescript"), 1);
|
|
360
|
-
var import_core2 = require("@formspec/core");
|
|
361
|
-
function extractJSDocConstraints(node) {
|
|
362
|
-
const results = [];
|
|
363
|
-
const jsDocTags = ts3.getJSDocTags(node);
|
|
364
|
-
for (const tag of jsDocTags) {
|
|
554
|
+
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
555
|
+
for (const tag of jsDocTagsAll) {
|
|
365
556
|
const tagName = tag.tagName.text;
|
|
366
|
-
if (!(tagName
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
const constraintName = tagName;
|
|
370
|
-
const expectedType = import_core2.CONSTRAINT_TAG_DEFINITIONS[constraintName];
|
|
557
|
+
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
371
558
|
const commentText = getTagCommentText(tag);
|
|
372
|
-
if (commentText === void 0 || commentText === "")
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
if (expectedType === "number") {
|
|
380
|
-
const value = Number(trimmed);
|
|
381
|
-
if (Number.isNaN(value)) {
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
results.push(createSyntheticDecorator(constraintName, value));
|
|
385
|
-
} else if (expectedType === "json") {
|
|
386
|
-
try {
|
|
387
|
-
const parsed = JSON.parse(trimmed);
|
|
388
|
-
if (!Array.isArray(parsed)) {
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
results.push(createSyntheticDecorator(constraintName, parsed));
|
|
392
|
-
} catch {
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
} else {
|
|
396
|
-
results.push(createSyntheticDecorator(constraintName, trimmed));
|
|
559
|
+
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
560
|
+
const text = commentText.trim();
|
|
561
|
+
const provenance = provenanceForJSDocTag(tag, file);
|
|
562
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
563
|
+
if (constraintNode) {
|
|
564
|
+
constraints.push(constraintNode);
|
|
397
565
|
}
|
|
398
566
|
}
|
|
399
|
-
return results;
|
|
400
|
-
}
|
|
401
|
-
function extractJSDocFieldMetadata(node) {
|
|
402
|
-
const jsDocTags = ts3.getJSDocTags(node);
|
|
403
567
|
let displayName;
|
|
404
568
|
let description;
|
|
405
|
-
|
|
569
|
+
let displayNameTag;
|
|
570
|
+
let descriptionTag;
|
|
571
|
+
for (const tag of jsDocTagsAll) {
|
|
406
572
|
const tagName = tag.tagName.text;
|
|
407
573
|
const commentText = getTagCommentText(tag);
|
|
408
574
|
if (commentText === void 0 || commentText.trim() === "") {
|
|
@@ -411,18 +577,132 @@ function extractJSDocFieldMetadata(node) {
|
|
|
411
577
|
const trimmed = commentText.trim();
|
|
412
578
|
if (tagName === "Field_displayName") {
|
|
413
579
|
displayName = trimmed;
|
|
580
|
+
displayNameTag = tag;
|
|
414
581
|
} else if (tagName === "Field_description") {
|
|
415
582
|
description = trimmed;
|
|
583
|
+
descriptionTag = tag;
|
|
416
584
|
}
|
|
417
585
|
}
|
|
418
|
-
if (displayName
|
|
586
|
+
if (displayName !== void 0 && displayNameTag) {
|
|
587
|
+
annotations.push({
|
|
588
|
+
kind: "annotation",
|
|
589
|
+
annotationKind: "displayName",
|
|
590
|
+
value: displayName,
|
|
591
|
+
provenance: provenanceForJSDocTag(displayNameTag, file)
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
if (description !== void 0 && descriptionTag) {
|
|
595
|
+
annotations.push({
|
|
596
|
+
kind: "annotation",
|
|
597
|
+
annotationKind: "description",
|
|
598
|
+
value: description,
|
|
599
|
+
provenance: provenanceForJSDocTag(descriptionTag, file)
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return { constraints, annotations };
|
|
603
|
+
}
|
|
604
|
+
function extractBlockText(block) {
|
|
605
|
+
return extractPlainText(block.content);
|
|
606
|
+
}
|
|
607
|
+
function extractPlainText(node) {
|
|
608
|
+
let result = "";
|
|
609
|
+
if (node instanceof import_tsdoc.DocPlainText) {
|
|
610
|
+
return node.text;
|
|
611
|
+
}
|
|
612
|
+
if (node instanceof import_tsdoc.DocSoftBreak) {
|
|
613
|
+
return " ";
|
|
614
|
+
}
|
|
615
|
+
if (typeof node.getChildNodes === "function") {
|
|
616
|
+
for (const child of node.getChildNodes()) {
|
|
617
|
+
result += extractPlainText(child);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
function parseConstraintValue(tagName, text, provenance) {
|
|
623
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
627
|
+
if (expectedType === "number") {
|
|
628
|
+
const value = Number(text);
|
|
629
|
+
if (Number.isNaN(value)) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
|
|
633
|
+
if (numericKind) {
|
|
634
|
+
return {
|
|
635
|
+
kind: "constraint",
|
|
636
|
+
constraintKind: numericKind,
|
|
637
|
+
value,
|
|
638
|
+
provenance
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
|
|
642
|
+
if (lengthKind) {
|
|
643
|
+
return {
|
|
644
|
+
kind: "constraint",
|
|
645
|
+
constraintKind: lengthKind,
|
|
646
|
+
value,
|
|
647
|
+
provenance
|
|
648
|
+
};
|
|
649
|
+
}
|
|
419
650
|
return null;
|
|
420
651
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
652
|
+
if (expectedType === "json") {
|
|
653
|
+
try {
|
|
654
|
+
const parsed = JSON.parse(text);
|
|
655
|
+
if (!Array.isArray(parsed)) {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
const members = [];
|
|
659
|
+
for (const item of parsed) {
|
|
660
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
661
|
+
members.push(item);
|
|
662
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
663
|
+
const id = item["id"];
|
|
664
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
665
|
+
members.push(id);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
kind: "constraint",
|
|
671
|
+
constraintKind: "allowedMembers",
|
|
672
|
+
members,
|
|
673
|
+
provenance
|
|
674
|
+
};
|
|
675
|
+
} catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
kind: "constraint",
|
|
681
|
+
constraintKind: "pattern",
|
|
682
|
+
pattern: text,
|
|
683
|
+
provenance
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
687
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
688
|
+
return {
|
|
689
|
+
surface: "tsdoc",
|
|
690
|
+
file,
|
|
691
|
+
line: line + 1,
|
|
692
|
+
column: character,
|
|
693
|
+
tagName: "@" + tagName
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function provenanceForJSDocTag(tag, file) {
|
|
697
|
+
const sourceFile = tag.getSourceFile();
|
|
698
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
699
|
+
return {
|
|
700
|
+
surface: "tsdoc",
|
|
701
|
+
file,
|
|
702
|
+
line: line + 1,
|
|
703
|
+
column: character,
|
|
704
|
+
tagName: "@" + tag.tagName.text
|
|
424
705
|
};
|
|
425
|
-
return createSyntheticDecorator("Field", fieldOpts);
|
|
426
706
|
}
|
|
427
707
|
function getTagCommentText(tag) {
|
|
428
708
|
if (tag.comment === void 0) {
|
|
@@ -431,27 +711,73 @@ function getTagCommentText(tag) {
|
|
|
431
711
|
if (typeof tag.comment === "string") {
|
|
432
712
|
return tag.comment;
|
|
433
713
|
}
|
|
434
|
-
return
|
|
714
|
+
return ts2.getTextOfJSDocComment(tag.comment);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/analyzer/jsdoc-constraints.ts
|
|
718
|
+
function extractJSDocConstraintNodes(node, file = "") {
|
|
719
|
+
const result = parseTSDocTags(node, file);
|
|
720
|
+
return [...result.constraints];
|
|
435
721
|
}
|
|
436
|
-
function
|
|
722
|
+
function extractJSDocAnnotationNodes(node, file = "") {
|
|
723
|
+
const result = parseTSDocTags(node, file);
|
|
724
|
+
return [...result.annotations];
|
|
725
|
+
}
|
|
726
|
+
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
727
|
+
if (!initializer) return null;
|
|
728
|
+
let value;
|
|
729
|
+
if (ts3.isStringLiteral(initializer)) {
|
|
730
|
+
value = initializer.text;
|
|
731
|
+
} else if (ts3.isNumericLiteral(initializer)) {
|
|
732
|
+
value = Number(initializer.text);
|
|
733
|
+
} else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
|
|
734
|
+
value = true;
|
|
735
|
+
} else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
|
|
736
|
+
value = false;
|
|
737
|
+
} else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
|
|
738
|
+
value = null;
|
|
739
|
+
} else if (ts3.isPrefixUnaryExpression(initializer)) {
|
|
740
|
+
if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
|
|
741
|
+
value = -Number(initializer.operand.text);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (value === void 0) return null;
|
|
745
|
+
const sourceFile = initializer.getSourceFile();
|
|
746
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(initializer.getStart());
|
|
437
747
|
return {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
748
|
+
kind: "annotation",
|
|
749
|
+
annotationKind: "defaultValue",
|
|
750
|
+
value,
|
|
751
|
+
provenance: {
|
|
752
|
+
surface: "tsdoc",
|
|
753
|
+
file,
|
|
754
|
+
line: line + 1,
|
|
755
|
+
column: character
|
|
756
|
+
}
|
|
441
757
|
};
|
|
442
758
|
}
|
|
443
759
|
|
|
444
760
|
// src/analyzer/class-analyzer.ts
|
|
445
|
-
function
|
|
761
|
+
function isObjectType(type) {
|
|
762
|
+
return !!(type.flags & ts4.TypeFlags.Object);
|
|
763
|
+
}
|
|
764
|
+
function isTypeReference(type) {
|
|
765
|
+
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
766
|
+
}
|
|
767
|
+
function analyzeClassToIR(classDecl, checker, file = "") {
|
|
446
768
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
447
769
|
const fields = [];
|
|
770
|
+
const fieldLayouts = [];
|
|
771
|
+
const typeRegistry = {};
|
|
772
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
448
773
|
const instanceMethods = [];
|
|
449
774
|
const staticMethods = [];
|
|
450
775
|
for (const member of classDecl.members) {
|
|
451
776
|
if (ts4.isPropertyDeclaration(member)) {
|
|
452
|
-
const
|
|
453
|
-
if (
|
|
454
|
-
fields.push(
|
|
777
|
+
const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
|
|
778
|
+
if (fieldNode) {
|
|
779
|
+
fields.push(fieldNode);
|
|
780
|
+
fieldLayouts.push({});
|
|
455
781
|
}
|
|
456
782
|
} else if (ts4.isMethodDeclaration(member)) {
|
|
457
783
|
const methodInfo = analyzeMethod(member, checker);
|
|
@@ -465,155 +791,25 @@ function analyzeClass(classDecl, checker) {
|
|
|
465
791
|
}
|
|
466
792
|
}
|
|
467
793
|
}
|
|
468
|
-
return {
|
|
469
|
-
name,
|
|
470
|
-
fields,
|
|
471
|
-
instanceMethods,
|
|
472
|
-
staticMethods
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
function analyzeField(prop, checker) {
|
|
476
|
-
if (!ts4.isIdentifier(prop.name)) {
|
|
477
|
-
return null;
|
|
478
|
-
}
|
|
479
|
-
const name = prop.name.text;
|
|
480
|
-
const typeNode = prop.type;
|
|
481
|
-
const type = checker.getTypeAtLocation(prop);
|
|
482
|
-
const optional = prop.questionToken !== void 0;
|
|
483
|
-
const decorators = extractDecorators(prop);
|
|
484
|
-
for (const dec of decorators) {
|
|
485
|
-
if (dec.node) {
|
|
486
|
-
const resolved = resolveDecorator(dec.node, checker);
|
|
487
|
-
if (resolved) {
|
|
488
|
-
dec.resolved = resolved;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
if (prop.type) {
|
|
493
|
-
const aliasConstraints = extractTypeAliasConstraints(prop.type, checker);
|
|
494
|
-
decorators.push(...aliasConstraints);
|
|
495
|
-
}
|
|
496
|
-
const jsdocConstraints = extractJSDocConstraints(prop);
|
|
497
|
-
decorators.push(...jsdocConstraints);
|
|
498
|
-
const deprecated = hasDeprecatedTag(prop);
|
|
499
|
-
const defaultValue = extractDefaultValue(prop.initializer);
|
|
500
|
-
return {
|
|
501
|
-
name,
|
|
502
|
-
typeNode,
|
|
503
|
-
type,
|
|
504
|
-
optional,
|
|
505
|
-
decorators,
|
|
506
|
-
deprecated,
|
|
507
|
-
defaultValue
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
function extractTypeAliasConstraints(typeNode, checker) {
|
|
511
|
-
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
512
|
-
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
513
|
-
if (!symbol?.declarations) return [];
|
|
514
|
-
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
515
|
-
if (!aliasDecl) return [];
|
|
516
|
-
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
517
|
-
return extractJSDocConstraints(aliasDecl);
|
|
518
|
-
}
|
|
519
|
-
function hasDeprecatedTag(node) {
|
|
520
|
-
const jsDocTags = ts4.getJSDocTags(node);
|
|
521
|
-
return jsDocTags.some((tag) => tag.tagName.text === "deprecated");
|
|
522
|
-
}
|
|
523
|
-
function extractDefaultValue(initializer) {
|
|
524
|
-
if (!initializer) return void 0;
|
|
525
|
-
if (ts4.isStringLiteral(initializer)) {
|
|
526
|
-
return initializer.text;
|
|
527
|
-
}
|
|
528
|
-
if (ts4.isNumericLiteral(initializer)) {
|
|
529
|
-
return Number(initializer.text);
|
|
530
|
-
}
|
|
531
|
-
if (initializer.kind === ts4.SyntaxKind.TrueKeyword) {
|
|
532
|
-
return true;
|
|
533
|
-
}
|
|
534
|
-
if (initializer.kind === ts4.SyntaxKind.FalseKeyword) {
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
if (initializer.kind === ts4.SyntaxKind.NullKeyword) {
|
|
538
|
-
return null;
|
|
539
|
-
}
|
|
540
|
-
if (ts4.isPrefixUnaryExpression(initializer)) {
|
|
541
|
-
if (initializer.operator === ts4.SyntaxKind.MinusToken && ts4.isNumericLiteral(initializer.operand)) {
|
|
542
|
-
return -Number(initializer.operand.text);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
return void 0;
|
|
546
|
-
}
|
|
547
|
-
function analyzeMethod(method, checker) {
|
|
548
|
-
if (!ts4.isIdentifier(method.name)) {
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
const name = method.name.text;
|
|
552
|
-
const parameters = [];
|
|
553
|
-
for (const param of method.parameters) {
|
|
554
|
-
if (ts4.isIdentifier(param.name)) {
|
|
555
|
-
const paramInfo = analyzeParameter(param, checker);
|
|
556
|
-
parameters.push(paramInfo);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
const returnTypeNode = method.type;
|
|
560
|
-
const signature = checker.getSignatureFromDeclaration(method);
|
|
561
|
-
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getTypeAtLocation(method);
|
|
562
|
-
return {
|
|
563
|
-
name,
|
|
564
|
-
parameters,
|
|
565
|
-
returnTypeNode,
|
|
566
|
-
returnType
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
function analyzeParameter(param, checker) {
|
|
570
|
-
const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
|
|
571
|
-
const typeNode = param.type;
|
|
572
|
-
const type = checker.getTypeAtLocation(param);
|
|
573
|
-
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
574
|
-
const optional = param.questionToken !== void 0 || param.initializer !== void 0;
|
|
575
|
-
return {
|
|
576
|
-
name,
|
|
577
|
-
typeNode,
|
|
578
|
-
type,
|
|
579
|
-
formSpecExportName,
|
|
580
|
-
optional
|
|
581
|
-
};
|
|
794
|
+
return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
|
|
582
795
|
}
|
|
583
|
-
function
|
|
584
|
-
if (!typeNode) return null;
|
|
585
|
-
if (!ts4.isTypeReferenceNode(typeNode)) return null;
|
|
586
|
-
const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
587
|
-
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
588
|
-
const typeArg = typeNode.typeArguments?.[0];
|
|
589
|
-
if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
|
|
590
|
-
if (ts4.isIdentifier(typeArg.exprName)) {
|
|
591
|
-
return typeArg.exprName.text;
|
|
592
|
-
}
|
|
593
|
-
if (ts4.isQualifiedName(typeArg.exprName)) {
|
|
594
|
-
return typeArg.exprName.right.text;
|
|
595
|
-
}
|
|
596
|
-
return null;
|
|
597
|
-
}
|
|
598
|
-
function analyzeInterface(interfaceDecl, checker) {
|
|
796
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
599
797
|
const name = interfaceDecl.name.text;
|
|
600
798
|
const fields = [];
|
|
799
|
+
const typeRegistry = {};
|
|
800
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
601
801
|
for (const member of interfaceDecl.members) {
|
|
602
802
|
if (ts4.isPropertySignature(member)) {
|
|
603
|
-
const
|
|
604
|
-
if (
|
|
605
|
-
fields.push(
|
|
803
|
+
const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
|
|
804
|
+
if (fieldNode) {
|
|
805
|
+
fields.push(fieldNode);
|
|
606
806
|
}
|
|
607
807
|
}
|
|
608
808
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
fields,
|
|
612
|
-
instanceMethods: [],
|
|
613
|
-
staticMethods: []
|
|
614
|
-
};
|
|
809
|
+
const fieldLayouts = fields.map(() => ({}));
|
|
810
|
+
return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
|
|
615
811
|
}
|
|
616
|
-
function
|
|
812
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
617
813
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
618
814
|
const sourceFile = typeAlias.getSourceFile();
|
|
619
815
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
@@ -625,11 +821,13 @@ function analyzeTypeAlias(typeAlias, checker) {
|
|
|
625
821
|
}
|
|
626
822
|
const name = typeAlias.name.text;
|
|
627
823
|
const fields = [];
|
|
824
|
+
const typeRegistry = {};
|
|
825
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
628
826
|
for (const member of typeAlias.type.members) {
|
|
629
827
|
if (ts4.isPropertySignature(member)) {
|
|
630
|
-
const
|
|
631
|
-
if (
|
|
632
|
-
fields.push(
|
|
828
|
+
const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
|
|
829
|
+
if (fieldNode) {
|
|
830
|
+
fields.push(fieldNode);
|
|
633
831
|
}
|
|
634
832
|
}
|
|
635
833
|
}
|
|
@@ -638,411 +836,601 @@ function analyzeTypeAlias(typeAlias, checker) {
|
|
|
638
836
|
analysis: {
|
|
639
837
|
name,
|
|
640
838
|
fields,
|
|
839
|
+
fieldLayouts: fields.map(() => ({})),
|
|
840
|
+
typeRegistry,
|
|
641
841
|
instanceMethods: [],
|
|
642
842
|
staticMethods: []
|
|
643
843
|
}
|
|
644
844
|
};
|
|
645
845
|
}
|
|
646
|
-
function
|
|
846
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
647
847
|
if (!ts4.isIdentifier(prop.name)) {
|
|
648
848
|
return null;
|
|
649
849
|
}
|
|
650
850
|
const name = prop.name.text;
|
|
651
|
-
const
|
|
652
|
-
const type = checker.getTypeAtLocation(prop);
|
|
851
|
+
const tsType = checker.getTypeAtLocation(prop);
|
|
653
852
|
const optional = prop.questionToken !== void 0;
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
853
|
+
const provenance = provenanceForNode(prop, file);
|
|
854
|
+
const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
855
|
+
const constraints = [];
|
|
856
|
+
if (prop.type) {
|
|
857
|
+
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
858
|
+
}
|
|
859
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
860
|
+
const annotations = [];
|
|
861
|
+
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
862
|
+
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
863
|
+
if (defaultAnnotation) {
|
|
864
|
+
annotations.push(defaultAnnotation);
|
|
865
|
+
}
|
|
666
866
|
return {
|
|
867
|
+
kind: "field",
|
|
667
868
|
name,
|
|
668
|
-
typeNode,
|
|
669
869
|
type,
|
|
670
|
-
optional,
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
870
|
+
required: !optional,
|
|
871
|
+
constraints,
|
|
872
|
+
annotations,
|
|
873
|
+
provenance
|
|
674
874
|
};
|
|
675
875
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
876
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
877
|
+
if (!ts4.isIdentifier(prop.name)) {
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
const name = prop.name.text;
|
|
881
|
+
const tsType = checker.getTypeAtLocation(prop);
|
|
882
|
+
const optional = prop.questionToken !== void 0;
|
|
883
|
+
const provenance = provenanceForNode(prop, file);
|
|
884
|
+
const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
885
|
+
const constraints = [];
|
|
886
|
+
if (prop.type) {
|
|
887
|
+
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
888
|
+
}
|
|
889
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
890
|
+
const annotations = [];
|
|
891
|
+
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
892
|
+
return {
|
|
893
|
+
kind: "field",
|
|
894
|
+
name,
|
|
895
|
+
type,
|
|
896
|
+
required: !optional,
|
|
897
|
+
constraints,
|
|
898
|
+
annotations,
|
|
899
|
+
provenance
|
|
900
|
+
};
|
|
683
901
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
902
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
903
|
+
if (type.flags & ts4.TypeFlags.String) {
|
|
904
|
+
return { kind: "primitive", primitiveKind: "string" };
|
|
905
|
+
}
|
|
906
|
+
if (type.flags & ts4.TypeFlags.Number) {
|
|
907
|
+
return { kind: "primitive", primitiveKind: "number" };
|
|
908
|
+
}
|
|
909
|
+
if (type.flags & ts4.TypeFlags.Boolean) {
|
|
910
|
+
return { kind: "primitive", primitiveKind: "boolean" };
|
|
911
|
+
}
|
|
912
|
+
if (type.flags & ts4.TypeFlags.Null) {
|
|
913
|
+
return { kind: "primitive", primitiveKind: "null" };
|
|
914
|
+
}
|
|
915
|
+
if (type.flags & ts4.TypeFlags.Undefined) {
|
|
916
|
+
return { kind: "primitive", primitiveKind: "null" };
|
|
917
|
+
}
|
|
918
|
+
if (type.isStringLiteral()) {
|
|
919
|
+
return {
|
|
920
|
+
kind: "enum",
|
|
921
|
+
members: [{ value: type.value }]
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
if (type.isNumberLiteral()) {
|
|
925
|
+
return {
|
|
926
|
+
kind: "enum",
|
|
927
|
+
members: [{ value: type.value }]
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
if (type.isUnion()) {
|
|
931
|
+
return resolveUnionType(type, checker, file, typeRegistry, visiting);
|
|
932
|
+
}
|
|
933
|
+
if (checker.isArrayType(type)) {
|
|
934
|
+
return resolveArrayType(type, checker, file, typeRegistry, visiting);
|
|
935
|
+
}
|
|
936
|
+
if (isObjectType(type)) {
|
|
937
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
938
|
+
}
|
|
939
|
+
return { kind: "primitive", primitiveKind: "string" };
|
|
940
|
+
}
|
|
941
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
942
|
+
const allTypes = type.types;
|
|
943
|
+
const nonNullTypes = allTypes.filter(
|
|
944
|
+
(t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
945
|
+
);
|
|
946
|
+
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
947
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
948
|
+
if (isBooleanUnion2) {
|
|
949
|
+
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
950
|
+
if (hasNull) {
|
|
951
|
+
return {
|
|
952
|
+
kind: "union",
|
|
953
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
return boolNode;
|
|
957
|
+
}
|
|
958
|
+
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
959
|
+
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
960
|
+
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
961
|
+
const enumNode = {
|
|
962
|
+
kind: "enum",
|
|
963
|
+
members: stringTypes.map((t) => ({ value: t.value }))
|
|
964
|
+
};
|
|
965
|
+
if (hasNull) {
|
|
966
|
+
return {
|
|
967
|
+
kind: "union",
|
|
968
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
return enumNode;
|
|
972
|
+
}
|
|
973
|
+
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
974
|
+
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
975
|
+
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
976
|
+
const enumNode = {
|
|
977
|
+
kind: "enum",
|
|
978
|
+
members: numberTypes.map((t) => ({ value: t.value }))
|
|
979
|
+
};
|
|
980
|
+
if (hasNull) {
|
|
981
|
+
return {
|
|
982
|
+
kind: "union",
|
|
983
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
return enumNode;
|
|
987
|
+
}
|
|
988
|
+
if (nonNullTypes.length === 1 && nonNullTypes[0]) {
|
|
989
|
+
const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
|
|
990
|
+
if (hasNull) {
|
|
991
|
+
return {
|
|
992
|
+
kind: "union",
|
|
993
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
return inner;
|
|
997
|
+
}
|
|
998
|
+
const members = nonNullTypes.map(
|
|
999
|
+
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
|
|
1000
|
+
);
|
|
1001
|
+
if (hasNull) {
|
|
1002
|
+
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1003
|
+
}
|
|
1004
|
+
return { kind: "union", members };
|
|
1005
|
+
}
|
|
1006
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1007
|
+
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1008
|
+
const elementType = typeArgs?.[0];
|
|
1009
|
+
const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
|
|
1010
|
+
return { kind: "array", items };
|
|
1011
|
+
}
|
|
1012
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1013
|
+
if (visiting.has(type)) {
|
|
1014
|
+
return { kind: "object", properties: [], additionalProperties: false };
|
|
1015
|
+
}
|
|
1016
|
+
visiting.add(type);
|
|
1017
|
+
const typeName = getNamedTypeName(type);
|
|
1018
|
+
if (typeName && typeName in typeRegistry) {
|
|
1019
|
+
visiting.delete(type);
|
|
1020
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1021
|
+
}
|
|
1022
|
+
const properties = [];
|
|
1023
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
|
|
1024
|
+
for (const prop of type.getProperties()) {
|
|
1025
|
+
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1026
|
+
if (!declaration) continue;
|
|
1027
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1028
|
+
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1029
|
+
const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
|
|
1030
|
+
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1031
|
+
properties.push({
|
|
1032
|
+
name: prop.name,
|
|
1033
|
+
type: propTypeNode,
|
|
1034
|
+
optional,
|
|
1035
|
+
constraints: fieldNodeInfo?.constraints ?? [],
|
|
1036
|
+
annotations: fieldNodeInfo?.annotations ?? [],
|
|
1037
|
+
provenance: fieldNodeInfo?.provenance ?? provenanceForFile(file)
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
visiting.delete(type);
|
|
1041
|
+
const objectNode = {
|
|
1042
|
+
kind: "object",
|
|
1043
|
+
properties,
|
|
1044
|
+
additionalProperties: false
|
|
1045
|
+
};
|
|
1046
|
+
if (typeName) {
|
|
1047
|
+
typeRegistry[typeName] = {
|
|
1048
|
+
name: typeName,
|
|
1049
|
+
type: objectNode,
|
|
1050
|
+
provenance: provenanceForFile(file)
|
|
1051
|
+
};
|
|
1052
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1053
|
+
}
|
|
1054
|
+
return objectNode;
|
|
1055
|
+
}
|
|
1056
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
687
1057
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
688
1058
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
689
1059
|
);
|
|
690
1060
|
for (const symbol of symbols) {
|
|
691
1061
|
const declarations = symbol.declarations;
|
|
692
1062
|
if (!declarations) continue;
|
|
693
|
-
const classDecl = declarations.find(
|
|
1063
|
+
const classDecl = declarations.find(ts4.isClassDeclaration);
|
|
694
1064
|
if (classDecl) {
|
|
695
1065
|
const map = /* @__PURE__ */ new Map();
|
|
696
1066
|
for (const member of classDecl.members) {
|
|
697
|
-
if (
|
|
698
|
-
const
|
|
699
|
-
if (
|
|
1067
|
+
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1068
|
+
const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
|
|
1069
|
+
if (fieldNode) {
|
|
1070
|
+
map.set(fieldNode.name, {
|
|
1071
|
+
constraints: [...fieldNode.constraints],
|
|
1072
|
+
annotations: [...fieldNode.annotations],
|
|
1073
|
+
provenance: fieldNode.provenance
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
700
1076
|
}
|
|
701
1077
|
}
|
|
702
1078
|
return map;
|
|
703
1079
|
}
|
|
704
|
-
const interfaceDecl = declarations.find(
|
|
1080
|
+
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
705
1081
|
if (interfaceDecl) {
|
|
706
|
-
return
|
|
1082
|
+
return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
|
|
707
1083
|
}
|
|
708
|
-
const typeAliasDecl = declarations.find(
|
|
709
|
-
if (typeAliasDecl &&
|
|
710
|
-
return
|
|
1084
|
+
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1085
|
+
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
1086
|
+
return buildFieldNodeInfoMap(
|
|
1087
|
+
typeAliasDecl.type.members,
|
|
1088
|
+
checker,
|
|
1089
|
+
file,
|
|
1090
|
+
typeRegistry,
|
|
1091
|
+
visiting
|
|
1092
|
+
);
|
|
711
1093
|
}
|
|
712
1094
|
}
|
|
713
1095
|
return null;
|
|
714
1096
|
}
|
|
715
|
-
function
|
|
1097
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
716
1098
|
const map = /* @__PURE__ */ new Map();
|
|
717
1099
|
for (const member of members) {
|
|
718
|
-
if (
|
|
719
|
-
const
|
|
720
|
-
if (
|
|
721
|
-
map.set(
|
|
1100
|
+
if (ts4.isPropertySignature(member)) {
|
|
1101
|
+
const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
|
|
1102
|
+
if (fieldNode) {
|
|
1103
|
+
map.set(fieldNode.name, {
|
|
1104
|
+
constraints: [...fieldNode.constraints],
|
|
1105
|
+
annotations: [...fieldNode.annotations],
|
|
1106
|
+
provenance: fieldNode.provenance
|
|
1107
|
+
});
|
|
722
1108
|
}
|
|
723
1109
|
}
|
|
724
1110
|
}
|
|
725
1111
|
return map;
|
|
726
1112
|
}
|
|
727
|
-
function
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const fieldInfo = fieldInfoMap?.get(prop.name) ?? void 0;
|
|
736
|
-
result.push({ name: prop.name, type: propType, optional, fieldInfo });
|
|
737
|
-
}
|
|
738
|
-
return result;
|
|
1113
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file) {
|
|
1114
|
+
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1115
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1116
|
+
if (!symbol?.declarations) return [];
|
|
1117
|
+
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1118
|
+
if (!aliasDecl) return [];
|
|
1119
|
+
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1120
|
+
return extractJSDocConstraintNodes(aliasDecl, file);
|
|
739
1121
|
}
|
|
740
|
-
function
|
|
741
|
-
|
|
1122
|
+
function provenanceForNode(node, file) {
|
|
1123
|
+
const sourceFile = node.getSourceFile();
|
|
1124
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
1125
|
+
return {
|
|
1126
|
+
surface: "tsdoc",
|
|
1127
|
+
file,
|
|
1128
|
+
line: line + 1,
|
|
1129
|
+
column: character
|
|
1130
|
+
};
|
|
742
1131
|
}
|
|
743
|
-
function
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
return { jsonSchema: { type: "null" }, formSpecFieldType: "null" };
|
|
755
|
-
}
|
|
756
|
-
if (type.flags & ts5.TypeFlags.Undefined) {
|
|
757
|
-
return { jsonSchema: {}, formSpecFieldType: "undefined" };
|
|
1132
|
+
function provenanceForFile(file) {
|
|
1133
|
+
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1134
|
+
}
|
|
1135
|
+
function getNamedTypeName(type) {
|
|
1136
|
+
const symbol = type.getSymbol();
|
|
1137
|
+
if (symbol?.declarations) {
|
|
1138
|
+
const decl = symbol.declarations[0];
|
|
1139
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
1140
|
+
const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
1141
|
+
if (name) return name;
|
|
1142
|
+
}
|
|
758
1143
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
1144
|
+
const aliasSymbol = type.aliasSymbol;
|
|
1145
|
+
if (aliasSymbol?.declarations) {
|
|
1146
|
+
const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1147
|
+
if (aliasDecl) {
|
|
1148
|
+
return aliasDecl.name.text;
|
|
1149
|
+
}
|
|
764
1150
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
function analyzeMethod(method, checker) {
|
|
1154
|
+
if (!ts4.isIdentifier(method.name)) {
|
|
1155
|
+
return null;
|
|
770
1156
|
}
|
|
771
|
-
|
|
772
|
-
|
|
1157
|
+
const name = method.name.text;
|
|
1158
|
+
const parameters = [];
|
|
1159
|
+
for (const param of method.parameters) {
|
|
1160
|
+
if (ts4.isIdentifier(param.name)) {
|
|
1161
|
+
const paramInfo = analyzeParameter(param, checker);
|
|
1162
|
+
parameters.push(paramInfo);
|
|
1163
|
+
}
|
|
773
1164
|
}
|
|
774
|
-
|
|
775
|
-
|
|
1165
|
+
const returnTypeNode = method.type;
|
|
1166
|
+
const signature = checker.getSignatureFromDeclaration(method);
|
|
1167
|
+
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getTypeAtLocation(method);
|
|
1168
|
+
return { name, parameters, returnTypeNode, returnType };
|
|
1169
|
+
}
|
|
1170
|
+
function analyzeParameter(param, checker) {
|
|
1171
|
+
const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
|
|
1172
|
+
const typeNode = param.type;
|
|
1173
|
+
const type = checker.getTypeAtLocation(param);
|
|
1174
|
+
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
1175
|
+
const optional = param.questionToken !== void 0 || param.initializer !== void 0;
|
|
1176
|
+
return { name, typeNode, type, formSpecExportName, optional };
|
|
1177
|
+
}
|
|
1178
|
+
function detectFormSpecReference(typeNode) {
|
|
1179
|
+
if (!typeNode) return null;
|
|
1180
|
+
if (!ts4.isTypeReferenceNode(typeNode)) return null;
|
|
1181
|
+
const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
1182
|
+
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
1183
|
+
const typeArg = typeNode.typeArguments?.[0];
|
|
1184
|
+
if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
|
|
1185
|
+
if (ts4.isIdentifier(typeArg.exprName)) {
|
|
1186
|
+
return typeArg.exprName.text;
|
|
776
1187
|
}
|
|
777
|
-
if (
|
|
778
|
-
return
|
|
1188
|
+
if (ts4.isQualifiedName(typeArg.exprName)) {
|
|
1189
|
+
return typeArg.exprName.right.text;
|
|
779
1190
|
}
|
|
780
|
-
return
|
|
1191
|
+
return null;
|
|
781
1192
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
jsonSchema: { type: "boolean" },
|
|
792
|
-
formSpecFieldType: "boolean"
|
|
793
|
-
};
|
|
794
|
-
if (hasNull) {
|
|
795
|
-
result.jsonSchema = { oneOf: [{ type: "boolean" }, { type: "null" }] };
|
|
796
|
-
}
|
|
797
|
-
return result;
|
|
1193
|
+
|
|
1194
|
+
// src/json-schema/ir-generator.ts
|
|
1195
|
+
function makeContext() {
|
|
1196
|
+
return { defs: {} };
|
|
1197
|
+
}
|
|
1198
|
+
function generateJsonSchemaFromIR(ir) {
|
|
1199
|
+
const ctx = makeContext();
|
|
1200
|
+
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1201
|
+
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
798
1202
|
}
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1203
|
+
const properties = {};
|
|
1204
|
+
const required = [];
|
|
1205
|
+
collectFields(ir.elements, properties, required, ctx);
|
|
1206
|
+
const uniqueRequired = [...new Set(required)];
|
|
1207
|
+
const result = {
|
|
1208
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1209
|
+
type: "object",
|
|
1210
|
+
properties,
|
|
1211
|
+
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
1212
|
+
};
|
|
1213
|
+
if (Object.keys(ctx.defs).length > 0) {
|
|
1214
|
+
result.$defs = ctx.defs;
|
|
810
1215
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1216
|
+
return result;
|
|
1217
|
+
}
|
|
1218
|
+
function collectFields(elements, properties, required, ctx) {
|
|
1219
|
+
for (const element of elements) {
|
|
1220
|
+
switch (element.kind) {
|
|
1221
|
+
case "field":
|
|
1222
|
+
properties[element.name] = generateFieldSchema(element, ctx);
|
|
1223
|
+
if (element.required) {
|
|
1224
|
+
required.push(element.name);
|
|
1225
|
+
}
|
|
1226
|
+
break;
|
|
1227
|
+
case "group":
|
|
1228
|
+
collectFields(element.elements, properties, required, ctx);
|
|
1229
|
+
break;
|
|
1230
|
+
case "conditional":
|
|
1231
|
+
collectFields(element.elements, properties, required, ctx);
|
|
1232
|
+
break;
|
|
1233
|
+
default: {
|
|
1234
|
+
const _exhaustive = element;
|
|
1235
|
+
void _exhaustive;
|
|
1236
|
+
}
|
|
820
1237
|
}
|
|
821
|
-
return result;
|
|
822
1238
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1239
|
+
}
|
|
1240
|
+
function generateFieldSchema(field, ctx) {
|
|
1241
|
+
const schema = generateTypeNode(field.type, ctx);
|
|
1242
|
+
applyConstraints(schema, field.constraints);
|
|
1243
|
+
applyAnnotations(schema, field.annotations);
|
|
1244
|
+
return schema;
|
|
1245
|
+
}
|
|
1246
|
+
function generateTypeNode(type, ctx) {
|
|
1247
|
+
switch (type.kind) {
|
|
1248
|
+
case "primitive":
|
|
1249
|
+
return generatePrimitiveType(type);
|
|
1250
|
+
case "enum":
|
|
1251
|
+
return generateEnumType(type);
|
|
1252
|
+
case "array":
|
|
1253
|
+
return generateArrayType(type, ctx);
|
|
1254
|
+
case "object":
|
|
1255
|
+
return generateObjectType(type, ctx);
|
|
1256
|
+
case "union":
|
|
1257
|
+
return generateUnionType(type, ctx);
|
|
1258
|
+
case "reference":
|
|
1259
|
+
return generateReferenceType(type);
|
|
1260
|
+
case "dynamic":
|
|
1261
|
+
return generateDynamicType(type);
|
|
1262
|
+
case "custom":
|
|
1263
|
+
return generateCustomType(type);
|
|
1264
|
+
default: {
|
|
1265
|
+
const _exhaustive = type;
|
|
1266
|
+
return _exhaustive;
|
|
827
1267
|
}
|
|
828
|
-
return result;
|
|
829
1268
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1269
|
+
}
|
|
1270
|
+
function generatePrimitiveType(type) {
|
|
1271
|
+
return { type: type.primitiveKind };
|
|
1272
|
+
}
|
|
1273
|
+
function generateEnumType(type) {
|
|
1274
|
+
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
1275
|
+
if (hasDisplayNames) {
|
|
1276
|
+
return {
|
|
1277
|
+
oneOf: type.members.map((m) => {
|
|
1278
|
+
const entry = { const: m.value };
|
|
1279
|
+
if (m.displayName !== void 0) {
|
|
1280
|
+
entry.title = m.displayName;
|
|
1281
|
+
}
|
|
1282
|
+
return entry;
|
|
1283
|
+
})
|
|
1284
|
+
};
|
|
833
1285
|
}
|
|
834
|
-
return {
|
|
835
|
-
jsonSchema: { oneOf: schemas },
|
|
836
|
-
formSpecFieldType: "union"
|
|
837
|
-
};
|
|
1286
|
+
return { enum: type.members.map((m) => m.value) };
|
|
838
1287
|
}
|
|
839
|
-
function
|
|
840
|
-
const typeArgs = type.typeArguments;
|
|
841
|
-
const elementType = typeArgs?.[0];
|
|
842
|
-
const itemSchema = elementType ? convertTypeInternal(elementType, checker, visiting).jsonSchema : {};
|
|
1288
|
+
function generateArrayType(type, ctx) {
|
|
843
1289
|
return {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
items: itemSchema
|
|
847
|
-
},
|
|
848
|
-
formSpecFieldType: "array"
|
|
1290
|
+
type: "array",
|
|
1291
|
+
items: generateTypeNode(type.items, ctx)
|
|
849
1292
|
};
|
|
850
1293
|
}
|
|
851
|
-
function
|
|
852
|
-
if (visiting.has(type)) {
|
|
853
|
-
return { jsonSchema: { type: "object" }, formSpecFieldType: "object" };
|
|
854
|
-
}
|
|
855
|
-
visiting.add(type);
|
|
1294
|
+
function generateObjectType(type, ctx) {
|
|
856
1295
|
const properties = {};
|
|
857
1296
|
const required = [];
|
|
858
|
-
for (const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
required.push(propInfo.name);
|
|
1297
|
+
for (const prop of type.properties) {
|
|
1298
|
+
properties[prop.name] = generatePropertySchema(prop, ctx);
|
|
1299
|
+
if (!prop.optional) {
|
|
1300
|
+
required.push(prop.name);
|
|
863
1301
|
}
|
|
864
1302
|
}
|
|
865
|
-
|
|
1303
|
+
const schema = { type: "object", properties };
|
|
1304
|
+
if (required.length > 0) {
|
|
1305
|
+
schema.required = required;
|
|
1306
|
+
}
|
|
1307
|
+
if (!type.additionalProperties) {
|
|
1308
|
+
schema.additionalProperties = false;
|
|
1309
|
+
}
|
|
1310
|
+
return schema;
|
|
1311
|
+
}
|
|
1312
|
+
function generatePropertySchema(prop, ctx) {
|
|
1313
|
+
const schema = generateTypeNode(prop.type, ctx);
|
|
1314
|
+
applyConstraints(schema, prop.constraints);
|
|
1315
|
+
applyAnnotations(schema, prop.annotations);
|
|
1316
|
+
return schema;
|
|
1317
|
+
}
|
|
1318
|
+
function generateUnionType(type, ctx) {
|
|
1319
|
+
if (isBooleanUnion(type)) {
|
|
1320
|
+
return { type: "boolean" };
|
|
1321
|
+
}
|
|
866
1322
|
return {
|
|
867
|
-
|
|
868
|
-
type: "object",
|
|
869
|
-
properties,
|
|
870
|
-
...required.length > 0 ? { required } : {}
|
|
871
|
-
},
|
|
872
|
-
formSpecFieldType: "object"
|
|
1323
|
+
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
873
1324
|
};
|
|
874
1325
|
}
|
|
875
|
-
function
|
|
876
|
-
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
if (
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
propInfo.name,
|
|
892
|
-
propInfo.type,
|
|
893
|
-
propInfo.fieldInfo?.decorators ?? [],
|
|
894
|
-
propInfo.optional,
|
|
895
|
-
checker,
|
|
896
|
-
visitedTypes
|
|
897
|
-
)
|
|
898
|
-
);
|
|
899
|
-
}
|
|
900
|
-
visitedTypes.delete(type);
|
|
901
|
-
if (nestedFields.length > 0) {
|
|
902
|
-
field.fields = nestedFields;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
for (const dec of decorators) {
|
|
907
|
-
applyDecoratorToField(field, dec);
|
|
908
|
-
}
|
|
909
|
-
return field;
|
|
910
|
-
}
|
|
911
|
-
function applyDecoratorToField(field, decorator) {
|
|
912
|
-
const { args } = decorator;
|
|
913
|
-
const resolved = decorator.resolved;
|
|
914
|
-
const effectiveName = resolved?.extendsBuiltin ?? decorator.name;
|
|
915
|
-
switch (effectiveName) {
|
|
916
|
-
case "Field": {
|
|
917
|
-
const opts = args[0];
|
|
918
|
-
if (typeof opts === "object" && opts !== null && !Array.isArray(opts)) {
|
|
919
|
-
if (typeof opts["displayName"] === "string") {
|
|
920
|
-
field.label = opts["displayName"];
|
|
921
|
-
}
|
|
922
|
-
if (typeof opts["description"] === "string") {
|
|
923
|
-
field.description = opts["description"];
|
|
924
|
-
}
|
|
925
|
-
if (typeof opts["placeholder"] === "string") {
|
|
926
|
-
field.placeholder = opts["placeholder"];
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
break;
|
|
1326
|
+
function isBooleanUnion(type) {
|
|
1327
|
+
if (type.members.length !== 2) return false;
|
|
1328
|
+
const kinds = type.members.map((m) => m.kind);
|
|
1329
|
+
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
1330
|
+
}
|
|
1331
|
+
function generateReferenceType(type) {
|
|
1332
|
+
return { $ref: `#/$defs/${type.name}` };
|
|
1333
|
+
}
|
|
1334
|
+
function generateDynamicType(type) {
|
|
1335
|
+
if (type.dynamicKind === "enum") {
|
|
1336
|
+
const schema = {
|
|
1337
|
+
type: "string",
|
|
1338
|
+
"x-formspec-source": type.sourceKey
|
|
1339
|
+
};
|
|
1340
|
+
if (type.parameterFields.length > 0) {
|
|
1341
|
+
schema["x-formspec-params"] = [...type.parameterFields];
|
|
930
1342
|
}
|
|
931
|
-
|
|
932
|
-
if (typeof args[0] === "number") {
|
|
933
|
-
field.min = args[0];
|
|
934
|
-
}
|
|
935
|
-
break;
|
|
936
|
-
case "Maximum":
|
|
937
|
-
if (typeof args[0] === "number") {
|
|
938
|
-
field.max = args[0];
|
|
939
|
-
}
|
|
940
|
-
break;
|
|
941
|
-
case "MinLength":
|
|
942
|
-
if (typeof args[0] === "number") {
|
|
943
|
-
field.minLength = args[0];
|
|
944
|
-
}
|
|
945
|
-
break;
|
|
946
|
-
case "MaxLength":
|
|
947
|
-
if (typeof args[0] === "number") {
|
|
948
|
-
field.maxLength = args[0];
|
|
949
|
-
}
|
|
950
|
-
break;
|
|
951
|
-
case "Pattern":
|
|
952
|
-
if (typeof args[0] === "string") {
|
|
953
|
-
field.pattern = args[0];
|
|
954
|
-
}
|
|
955
|
-
break;
|
|
956
|
-
case "EnumOptions":
|
|
957
|
-
if (Array.isArray(args[0])) {
|
|
958
|
-
field.options = args[0];
|
|
959
|
-
}
|
|
960
|
-
break;
|
|
961
|
-
case "ShowWhen":
|
|
962
|
-
if (typeof args[0] === "object" && args[0] !== null) {
|
|
963
|
-
field.showWhen = args[0];
|
|
964
|
-
}
|
|
965
|
-
break;
|
|
966
|
-
case "Group":
|
|
967
|
-
if (typeof args[0] === "string") {
|
|
968
|
-
field.group = args[0];
|
|
969
|
-
}
|
|
970
|
-
break;
|
|
1343
|
+
return schema;
|
|
971
1344
|
}
|
|
1345
|
+
return {
|
|
1346
|
+
type: "object",
|
|
1347
|
+
additionalProperties: true,
|
|
1348
|
+
"x-formspec-schemaSource": type.sourceKey
|
|
1349
|
+
};
|
|
972
1350
|
}
|
|
973
|
-
function
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1351
|
+
function generateCustomType(_type) {
|
|
1352
|
+
return { type: "object" };
|
|
1353
|
+
}
|
|
1354
|
+
function applyConstraints(schema, constraints) {
|
|
1355
|
+
for (const constraint of constraints) {
|
|
1356
|
+
switch (constraint.constraintKind) {
|
|
1357
|
+
case "minimum":
|
|
1358
|
+
schema.minimum = constraint.value;
|
|
1359
|
+
break;
|
|
1360
|
+
case "maximum":
|
|
1361
|
+
schema.maximum = constraint.value;
|
|
1362
|
+
break;
|
|
1363
|
+
case "exclusiveMinimum":
|
|
1364
|
+
schema.exclusiveMinimum = constraint.value;
|
|
1365
|
+
break;
|
|
1366
|
+
case "exclusiveMaximum":
|
|
1367
|
+
schema.exclusiveMaximum = constraint.value;
|
|
1368
|
+
break;
|
|
1369
|
+
case "multipleOf": {
|
|
1370
|
+
const { value } = constraint;
|
|
1371
|
+
if (value === 1 && schema.type === "number") {
|
|
1372
|
+
schema.type = "integer";
|
|
1373
|
+
} else {
|
|
1374
|
+
schema.multipleOf = value;
|
|
989
1375
|
}
|
|
990
1376
|
break;
|
|
991
1377
|
}
|
|
992
|
-
case "
|
|
993
|
-
|
|
994
|
-
result.minimum = args[0];
|
|
995
|
-
}
|
|
1378
|
+
case "minLength":
|
|
1379
|
+
schema.minLength = constraint.value;
|
|
996
1380
|
break;
|
|
997
|
-
case "
|
|
998
|
-
|
|
999
|
-
result.maximum = args[0];
|
|
1000
|
-
}
|
|
1381
|
+
case "maxLength":
|
|
1382
|
+
schema.maxLength = constraint.value;
|
|
1001
1383
|
break;
|
|
1002
|
-
case "
|
|
1003
|
-
|
|
1004
|
-
result.exclusiveMinimum = args[0];
|
|
1005
|
-
}
|
|
1384
|
+
case "minItems":
|
|
1385
|
+
schema.minItems = constraint.value;
|
|
1006
1386
|
break;
|
|
1007
|
-
case "
|
|
1008
|
-
|
|
1009
|
-
result.exclusiveMaximum = args[0];
|
|
1010
|
-
}
|
|
1387
|
+
case "maxItems":
|
|
1388
|
+
schema.maxItems = constraint.value;
|
|
1011
1389
|
break;
|
|
1012
|
-
case "
|
|
1013
|
-
|
|
1014
|
-
result.minLength = args[0];
|
|
1015
|
-
}
|
|
1390
|
+
case "pattern":
|
|
1391
|
+
schema.pattern = constraint.pattern;
|
|
1016
1392
|
break;
|
|
1017
|
-
case "
|
|
1018
|
-
|
|
1019
|
-
result.maxLength = args[0];
|
|
1020
|
-
}
|
|
1393
|
+
case "uniqueItems":
|
|
1394
|
+
schema.uniqueItems = constraint.value;
|
|
1021
1395
|
break;
|
|
1022
|
-
case "
|
|
1023
|
-
if (typeof args[0] === "string") {
|
|
1024
|
-
result.pattern = args[0];
|
|
1025
|
-
}
|
|
1396
|
+
case "allowedMembers":
|
|
1026
1397
|
break;
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
} else {
|
|
1033
|
-
setSchemaExtension(result, key, args[0] ?? true);
|
|
1398
|
+
case "custom":
|
|
1399
|
+
break;
|
|
1400
|
+
default: {
|
|
1401
|
+
const _exhaustive = constraint;
|
|
1402
|
+
void _exhaustive;
|
|
1034
1403
|
}
|
|
1035
1404
|
}
|
|
1036
1405
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1406
|
+
}
|
|
1407
|
+
function applyAnnotations(schema, annotations) {
|
|
1408
|
+
for (const annotation of annotations) {
|
|
1409
|
+
switch (annotation.annotationKind) {
|
|
1410
|
+
case "displayName":
|
|
1411
|
+
schema.title = annotation.value;
|
|
1412
|
+
break;
|
|
1413
|
+
case "description":
|
|
1414
|
+
schema.description = annotation.value;
|
|
1415
|
+
break;
|
|
1416
|
+
case "defaultValue":
|
|
1417
|
+
schema.default = annotation.value;
|
|
1418
|
+
break;
|
|
1419
|
+
case "deprecated":
|
|
1420
|
+
schema.deprecated = true;
|
|
1421
|
+
break;
|
|
1422
|
+
case "placeholder":
|
|
1423
|
+
break;
|
|
1424
|
+
case "formatHint":
|
|
1425
|
+
break;
|
|
1426
|
+
case "custom":
|
|
1427
|
+
break;
|
|
1428
|
+
default: {
|
|
1429
|
+
const _exhaustive = annotation;
|
|
1430
|
+
void _exhaustive;
|
|
1431
|
+
}
|
|
1043
1432
|
}
|
|
1044
1433
|
}
|
|
1045
|
-
return result;
|
|
1046
1434
|
}
|
|
1047
1435
|
|
|
1048
1436
|
// src/ui-schema/schema.ts
|
|
@@ -1153,7 +1541,7 @@ var uiSchema = import_zod.z.lazy(
|
|
|
1153
1541
|
() => import_zod.z.union([verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema])
|
|
1154
1542
|
);
|
|
1155
1543
|
|
|
1156
|
-
// src/ui-schema/generator.ts
|
|
1544
|
+
// src/ui-schema/ir-generator.ts
|
|
1157
1545
|
var import_zod2 = require("zod");
|
|
1158
1546
|
function parseOrThrow(schema, value, label) {
|
|
1159
1547
|
try {
|
|
@@ -1168,101 +1556,488 @@ ${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
|
|
|
1168
1556
|
throw error;
|
|
1169
1557
|
}
|
|
1170
1558
|
}
|
|
1171
|
-
function
|
|
1559
|
+
function fieldToScope(fieldName) {
|
|
1560
|
+
return `#/properties/${fieldName}`;
|
|
1561
|
+
}
|
|
1562
|
+
function createShowRule(fieldName, value) {
|
|
1563
|
+
return {
|
|
1564
|
+
effect: "SHOW",
|
|
1565
|
+
condition: {
|
|
1566
|
+
scope: fieldToScope(fieldName),
|
|
1567
|
+
schema: { const: value }
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
function combineRules(parentRule, childRule) {
|
|
1572
|
+
const parentCondition = parentRule.condition;
|
|
1573
|
+
const childCondition = childRule.condition;
|
|
1574
|
+
return {
|
|
1575
|
+
effect: "SHOW",
|
|
1576
|
+
condition: {
|
|
1577
|
+
scope: "#",
|
|
1578
|
+
schema: {
|
|
1579
|
+
allOf: [
|
|
1580
|
+
{
|
|
1581
|
+
properties: {
|
|
1582
|
+
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
1583
|
+
}
|
|
1584
|
+
},
|
|
1585
|
+
{
|
|
1586
|
+
properties: {
|
|
1587
|
+
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
]
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
function fieldNodeToControl(field, parentRule) {
|
|
1596
|
+
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
1172
1597
|
const control = {
|
|
1173
1598
|
type: "Control",
|
|
1174
|
-
scope:
|
|
1599
|
+
scope: fieldToScope(field.name),
|
|
1600
|
+
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
1601
|
+
...parentRule !== void 0 && { rule: parentRule }
|
|
1175
1602
|
};
|
|
1176
|
-
if (field.label !== void 0) {
|
|
1177
|
-
control.label = field.label;
|
|
1178
|
-
}
|
|
1179
|
-
if (field.showWhen !== void 0 && typeof field.showWhen === "object" && "field" in field.showWhen && "value" in field.showWhen) {
|
|
1180
|
-
const sw = field.showWhen;
|
|
1181
|
-
control.rule = {
|
|
1182
|
-
effect: "SHOW",
|
|
1183
|
-
condition: {
|
|
1184
|
-
scope: `#/properties/${sw.field}`,
|
|
1185
|
-
schema: { const: sw.value }
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
1603
|
return control;
|
|
1190
1604
|
}
|
|
1191
|
-
function
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1605
|
+
function groupNodeToLayout(group, parentRule) {
|
|
1606
|
+
return {
|
|
1607
|
+
type: "Group",
|
|
1608
|
+
label: group.label,
|
|
1609
|
+
elements: irElementsToUiSchema(group.elements, parentRule),
|
|
1610
|
+
...parentRule !== void 0 && { rule: parentRule }
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
function irElementsToUiSchema(elements, parentRule) {
|
|
1614
|
+
const result = [];
|
|
1615
|
+
for (const element of elements) {
|
|
1616
|
+
switch (element.kind) {
|
|
1617
|
+
case "field": {
|
|
1618
|
+
result.push(fieldNodeToControl(element, parentRule));
|
|
1619
|
+
break;
|
|
1201
1620
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1621
|
+
case "group": {
|
|
1622
|
+
result.push(groupNodeToLayout(element, parentRule));
|
|
1623
|
+
break;
|
|
1624
|
+
}
|
|
1625
|
+
case "conditional": {
|
|
1626
|
+
const newRule = createShowRule(element.fieldName, element.value);
|
|
1627
|
+
const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
|
|
1628
|
+
const childElements = irElementsToUiSchema(element.elements, combinedRule);
|
|
1629
|
+
result.push(...childElements);
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1632
|
+
default: {
|
|
1633
|
+
const _exhaustive = element;
|
|
1634
|
+
void _exhaustive;
|
|
1635
|
+
throw new Error("Unhandled IR element kind");
|
|
1215
1636
|
}
|
|
1216
|
-
} else {
|
|
1217
|
-
const groupElements = groupMap.get(key) ?? [];
|
|
1218
|
-
const groupLayout = {
|
|
1219
|
-
type: "Group",
|
|
1220
|
-
label: key,
|
|
1221
|
-
elements: groupElements
|
|
1222
|
-
};
|
|
1223
|
-
elements.push(groupLayout);
|
|
1224
1637
|
}
|
|
1225
1638
|
}
|
|
1639
|
+
return result;
|
|
1640
|
+
}
|
|
1641
|
+
function generateUiSchemaFromIR(ir) {
|
|
1226
1642
|
const result = {
|
|
1227
1643
|
type: "VerticalLayout",
|
|
1228
|
-
elements
|
|
1644
|
+
elements: irElementsToUiSchema(ir.elements)
|
|
1229
1645
|
};
|
|
1230
1646
|
return parseOrThrow(uiSchema, result, "UI Schema");
|
|
1231
1647
|
}
|
|
1232
1648
|
|
|
1233
1649
|
// src/generators/class-schema.ts
|
|
1234
|
-
function generateClassSchemas(analysis,
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1650
|
+
function generateClassSchemas(analysis, source) {
|
|
1651
|
+
const ir = canonicalizeTSDoc(analysis, source);
|
|
1652
|
+
return {
|
|
1653
|
+
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
1654
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// src/validate/constraint-validator.ts
|
|
1659
|
+
function makeCode(ctx, category, number) {
|
|
1660
|
+
return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
|
|
1661
|
+
}
|
|
1662
|
+
function addContradiction(ctx, message, primary, related) {
|
|
1663
|
+
ctx.diagnostics.push({
|
|
1664
|
+
code: makeCode(ctx, "CONTRADICTION", 1),
|
|
1665
|
+
message,
|
|
1666
|
+
severity: "error",
|
|
1667
|
+
primaryLocation: primary,
|
|
1668
|
+
relatedLocations: [related]
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
function addTypeMismatch(ctx, message, primary) {
|
|
1672
|
+
ctx.diagnostics.push({
|
|
1673
|
+
code: makeCode(ctx, "TYPE_MISMATCH", 1),
|
|
1674
|
+
message,
|
|
1675
|
+
severity: "error",
|
|
1676
|
+
primaryLocation: primary,
|
|
1677
|
+
relatedLocations: []
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
function addUnknownExtension(ctx, message, primary) {
|
|
1681
|
+
ctx.diagnostics.push({
|
|
1682
|
+
code: makeCode(ctx, "UNKNOWN_EXTENSION", 1),
|
|
1683
|
+
message,
|
|
1684
|
+
severity: "warning",
|
|
1685
|
+
primaryLocation: primary,
|
|
1686
|
+
relatedLocations: []
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
function findNumeric(constraints, constraintKind) {
|
|
1690
|
+
return constraints.find(
|
|
1691
|
+
(c) => c.constraintKind === constraintKind
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
function findLength(constraints, constraintKind) {
|
|
1695
|
+
return constraints.find(
|
|
1696
|
+
(c) => c.constraintKind === constraintKind
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
function findAllowedMembers(constraints) {
|
|
1700
|
+
return constraints.filter(
|
|
1701
|
+
(c) => c.constraintKind === "allowedMembers"
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1705
|
+
const min = findNumeric(constraints, "minimum");
|
|
1706
|
+
const max = findNumeric(constraints, "maximum");
|
|
1707
|
+
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
1708
|
+
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
1709
|
+
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
1710
|
+
addContradiction(
|
|
1711
|
+
ctx,
|
|
1712
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
1713
|
+
min.provenance,
|
|
1714
|
+
max.provenance
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
1718
|
+
addContradiction(
|
|
1719
|
+
ctx,
|
|
1720
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
1721
|
+
exMin.provenance,
|
|
1722
|
+
max.provenance
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
1726
|
+
addContradiction(
|
|
1727
|
+
ctx,
|
|
1728
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
1729
|
+
min.provenance,
|
|
1730
|
+
exMax.provenance
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
1734
|
+
addContradiction(
|
|
1735
|
+
ctx,
|
|
1736
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
1737
|
+
exMin.provenance,
|
|
1738
|
+
exMax.provenance
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
1743
|
+
const minLen = findLength(constraints, "minLength");
|
|
1744
|
+
const maxLen = findLength(constraints, "maxLength");
|
|
1745
|
+
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
1746
|
+
addContradiction(
|
|
1747
|
+
ctx,
|
|
1748
|
+
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
1749
|
+
minLen.provenance,
|
|
1750
|
+
maxLen.provenance
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
const minItems = findLength(constraints, "minItems");
|
|
1754
|
+
const maxItems = findLength(constraints, "maxItems");
|
|
1755
|
+
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
1756
|
+
addContradiction(
|
|
1757
|
+
ctx,
|
|
1758
|
+
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
1759
|
+
minItems.provenance,
|
|
1760
|
+
maxItems.provenance
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
1765
|
+
const members = findAllowedMembers(constraints);
|
|
1766
|
+
if (members.length < 2) return;
|
|
1767
|
+
const firstSet = new Set(members[0]?.members ?? []);
|
|
1768
|
+
for (let i = 1; i < members.length; i++) {
|
|
1769
|
+
const current = members[i];
|
|
1770
|
+
if (current === void 0) continue;
|
|
1771
|
+
for (const m of firstSet) {
|
|
1772
|
+
if (!current.members.includes(m)) {
|
|
1773
|
+
firstSet.delete(m);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
if (firstSet.size === 0) {
|
|
1778
|
+
const first = members[0];
|
|
1779
|
+
const second = members[1];
|
|
1780
|
+
if (first !== void 0 && second !== void 0) {
|
|
1781
|
+
addContradiction(
|
|
1782
|
+
ctx,
|
|
1783
|
+
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
1784
|
+
first.provenance,
|
|
1785
|
+
second.provenance
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function typeLabel(type) {
|
|
1791
|
+
switch (type.kind) {
|
|
1792
|
+
case "primitive":
|
|
1793
|
+
return type.primitiveKind;
|
|
1794
|
+
case "enum":
|
|
1795
|
+
return "enum";
|
|
1796
|
+
case "array":
|
|
1797
|
+
return "array";
|
|
1798
|
+
case "object":
|
|
1799
|
+
return "object";
|
|
1800
|
+
case "union":
|
|
1801
|
+
return "union";
|
|
1802
|
+
case "reference":
|
|
1803
|
+
return `reference(${type.name})`;
|
|
1804
|
+
case "dynamic":
|
|
1805
|
+
return `dynamic(${type.dynamicKind})`;
|
|
1806
|
+
case "custom":
|
|
1807
|
+
return `custom(${type.typeId})`;
|
|
1808
|
+
default: {
|
|
1809
|
+
const _exhaustive = type;
|
|
1810
|
+
return String(_exhaustive);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
1815
|
+
const isNumber = type.kind === "primitive" && type.primitiveKind === "number";
|
|
1816
|
+
const isString = type.kind === "primitive" && type.primitiveKind === "string";
|
|
1817
|
+
const isArray = type.kind === "array";
|
|
1818
|
+
const isEnum = type.kind === "enum";
|
|
1819
|
+
const label = typeLabel(type);
|
|
1820
|
+
for (const constraint of constraints) {
|
|
1821
|
+
const ck = constraint.constraintKind;
|
|
1822
|
+
switch (ck) {
|
|
1823
|
+
case "minimum":
|
|
1824
|
+
case "maximum":
|
|
1825
|
+
case "exclusiveMinimum":
|
|
1826
|
+
case "exclusiveMaximum":
|
|
1827
|
+
case "multipleOf": {
|
|
1828
|
+
if (!isNumber) {
|
|
1829
|
+
addTypeMismatch(
|
|
1830
|
+
ctx,
|
|
1831
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1832
|
+
constraint.provenance
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1837
|
+
case "minLength":
|
|
1838
|
+
case "maxLength":
|
|
1839
|
+
case "pattern": {
|
|
1840
|
+
if (!isString) {
|
|
1841
|
+
addTypeMismatch(
|
|
1842
|
+
ctx,
|
|
1843
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
1844
|
+
constraint.provenance
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
case "minItems":
|
|
1850
|
+
case "maxItems":
|
|
1851
|
+
case "uniqueItems": {
|
|
1852
|
+
if (!isArray) {
|
|
1853
|
+
addTypeMismatch(
|
|
1854
|
+
ctx,
|
|
1855
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
1856
|
+
constraint.provenance
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
break;
|
|
1860
|
+
}
|
|
1861
|
+
case "allowedMembers": {
|
|
1862
|
+
if (!isEnum) {
|
|
1863
|
+
addTypeMismatch(
|
|
1864
|
+
ctx,
|
|
1865
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
1866
|
+
constraint.provenance
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
break;
|
|
1870
|
+
}
|
|
1871
|
+
case "custom": {
|
|
1872
|
+
checkCustomConstraint(ctx, fieldName, type, constraint);
|
|
1873
|
+
break;
|
|
1874
|
+
}
|
|
1875
|
+
default: {
|
|
1876
|
+
const _exhaustive = constraint;
|
|
1877
|
+
throw new Error(
|
|
1878
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1244
1881
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
1885
|
+
if (ctx.extensionRegistry === void 0) return;
|
|
1886
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
1887
|
+
if (registration === void 0) {
|
|
1888
|
+
addUnknownExtension(
|
|
1889
|
+
ctx,
|
|
1890
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
1891
|
+
constraint.provenance
|
|
1892
|
+
);
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
if (registration.applicableTypes === null) return;
|
|
1896
|
+
if (!registration.applicableTypes.includes(type.kind)) {
|
|
1897
|
+
addTypeMismatch(
|
|
1898
|
+
ctx,
|
|
1899
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1900
|
+
constraint.provenance
|
|
1251
1901
|
);
|
|
1252
|
-
uiElements.push(formSpecField);
|
|
1253
1902
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1903
|
+
}
|
|
1904
|
+
function validateFieldNode(ctx, field) {
|
|
1905
|
+
validateConstraints(ctx, field.name, field.type, field.constraints);
|
|
1906
|
+
if (field.type.kind === "object") {
|
|
1907
|
+
for (const prop of field.type.properties) {
|
|
1908
|
+
validateObjectProperty(ctx, field.name, prop);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
function validateObjectProperty(ctx, parentName, prop) {
|
|
1913
|
+
const qualifiedName = `${parentName}.${prop.name}`;
|
|
1914
|
+
validateConstraints(ctx, qualifiedName, prop.type, prop.constraints);
|
|
1915
|
+
if (prop.type.kind === "object") {
|
|
1916
|
+
for (const nestedProp of prop.type.properties) {
|
|
1917
|
+
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
function validateConstraints(ctx, name, type, constraints) {
|
|
1922
|
+
checkNumericContradictions(ctx, name, constraints);
|
|
1923
|
+
checkLengthContradictions(ctx, name, constraints);
|
|
1924
|
+
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1925
|
+
checkTypeApplicability(ctx, name, type, constraints);
|
|
1926
|
+
}
|
|
1927
|
+
function validateElement(ctx, element) {
|
|
1928
|
+
switch (element.kind) {
|
|
1929
|
+
case "field":
|
|
1930
|
+
validateFieldNode(ctx, element);
|
|
1931
|
+
break;
|
|
1932
|
+
case "group":
|
|
1933
|
+
for (const child of element.elements) {
|
|
1934
|
+
validateElement(ctx, child);
|
|
1935
|
+
}
|
|
1936
|
+
break;
|
|
1937
|
+
case "conditional":
|
|
1938
|
+
for (const child of element.elements) {
|
|
1939
|
+
validateElement(ctx, child);
|
|
1940
|
+
}
|
|
1941
|
+
break;
|
|
1942
|
+
default: {
|
|
1943
|
+
const _exhaustive = element;
|
|
1944
|
+
throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
function validateIR(ir, options) {
|
|
1949
|
+
const ctx = {
|
|
1950
|
+
diagnostics: [],
|
|
1951
|
+
vendorPrefix: options?.vendorPrefix ?? "FORMSPEC",
|
|
1952
|
+
extensionRegistry: options?.extensionRegistry
|
|
1953
|
+
};
|
|
1954
|
+
for (const element of ir.elements) {
|
|
1955
|
+
validateElement(ctx, element);
|
|
1956
|
+
}
|
|
1957
|
+
return {
|
|
1958
|
+
diagnostics: ctx.diagnostics,
|
|
1959
|
+
valid: ctx.diagnostics.every((d) => d.severity !== "error")
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// src/extensions/registry.ts
|
|
1964
|
+
function createExtensionRegistry(extensions) {
|
|
1965
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1966
|
+
const constraintMap = /* @__PURE__ */ new Map();
|
|
1967
|
+
const annotationMap = /* @__PURE__ */ new Map();
|
|
1968
|
+
for (const ext of extensions) {
|
|
1969
|
+
if (ext.types !== void 0) {
|
|
1970
|
+
for (const type of ext.types) {
|
|
1971
|
+
const qualifiedId = `${ext.extensionId}/${type.typeName}`;
|
|
1972
|
+
if (typeMap.has(qualifiedId)) {
|
|
1973
|
+
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1974
|
+
}
|
|
1975
|
+
typeMap.set(qualifiedId, type);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
if (ext.constraints !== void 0) {
|
|
1979
|
+
for (const constraint of ext.constraints) {
|
|
1980
|
+
const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
|
|
1981
|
+
if (constraintMap.has(qualifiedId)) {
|
|
1982
|
+
throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
|
|
1983
|
+
}
|
|
1984
|
+
constraintMap.set(qualifiedId, constraint);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
if (ext.annotations !== void 0) {
|
|
1988
|
+
for (const annotation of ext.annotations) {
|
|
1989
|
+
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
1990
|
+
if (annotationMap.has(qualifiedId)) {
|
|
1991
|
+
throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
|
|
1992
|
+
}
|
|
1993
|
+
annotationMap.set(qualifiedId, annotation);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return {
|
|
1998
|
+
extensions,
|
|
1999
|
+
findType: (typeId) => typeMap.get(typeId),
|
|
2000
|
+
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
2001
|
+
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1258
2002
|
};
|
|
1259
|
-
const uiSchema2 = generateUiSchemaFromFields(uiElements);
|
|
1260
|
-
return { jsonSchema, uiSchema: uiSchema2 };
|
|
1261
2003
|
}
|
|
1262
2004
|
|
|
1263
2005
|
// src/generators/method-schema.ts
|
|
2006
|
+
var import_core5 = require("@formspec/core");
|
|
2007
|
+
function typeToJsonSchema(type, checker) {
|
|
2008
|
+
const typeRegistry = {};
|
|
2009
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2010
|
+
const typeNode = resolveTypeNode(type, checker, "", typeRegistry, visiting);
|
|
2011
|
+
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
2012
|
+
const ir = {
|
|
2013
|
+
kind: "form-ir",
|
|
2014
|
+
irVersion: import_core5.IR_VERSION,
|
|
2015
|
+
elements: [
|
|
2016
|
+
{
|
|
2017
|
+
kind: "field",
|
|
2018
|
+
name: "__result",
|
|
2019
|
+
type: typeNode,
|
|
2020
|
+
required: true,
|
|
2021
|
+
constraints: [],
|
|
2022
|
+
annotations: [],
|
|
2023
|
+
provenance: fieldProvenance
|
|
2024
|
+
}
|
|
2025
|
+
],
|
|
2026
|
+
typeRegistry,
|
|
2027
|
+
provenance: fieldProvenance
|
|
2028
|
+
};
|
|
2029
|
+
const schema = generateJsonSchemaFromIR(ir);
|
|
2030
|
+
const fieldSchema = schema.properties?.["__result"];
|
|
2031
|
+
if (fieldSchema) {
|
|
2032
|
+
if (schema.$defs && Object.keys(schema.$defs).length > 0) {
|
|
2033
|
+
return { ...fieldSchema, $defs: schema.$defs };
|
|
2034
|
+
}
|
|
2035
|
+
return fieldSchema;
|
|
2036
|
+
}
|
|
2037
|
+
return { type: "object" };
|
|
2038
|
+
}
|
|
1264
2039
|
function generateMethodSchemas(method, checker, loadedFormSpecs) {
|
|
1265
|
-
const returnType =
|
|
2040
|
+
const returnType = typeToJsonSchema(method.returnType, checker);
|
|
1266
2041
|
const params = generateParamsSchemas(method.parameters, checker, loadedFormSpecs);
|
|
1267
2042
|
return {
|
|
1268
2043
|
name: method.name,
|
|
@@ -1291,7 +2066,7 @@ function generateParamsSchemas(parameters, checker, loadedFormSpecs) {
|
|
|
1291
2066
|
}
|
|
1292
2067
|
if (parameters.length === 1 && parameters[0]) {
|
|
1293
2068
|
const param = parameters[0];
|
|
1294
|
-
const jsonSchema =
|
|
2069
|
+
const jsonSchema = typeToJsonSchema(param.type, checker);
|
|
1295
2070
|
return {
|
|
1296
2071
|
jsonSchema,
|
|
1297
2072
|
uiSchema: null,
|
|
@@ -1301,7 +2076,7 @@ function generateParamsSchemas(parameters, checker, loadedFormSpecs) {
|
|
|
1301
2076
|
const properties = {};
|
|
1302
2077
|
const required = [];
|
|
1303
2078
|
for (const param of parameters) {
|
|
1304
|
-
const paramSchema =
|
|
2079
|
+
const paramSchema = typeToJsonSchema(param.type, checker);
|
|
1305
2080
|
properties[param.name] = paramSchema;
|
|
1306
2081
|
if (!param.optional) {
|
|
1307
2082
|
required.push(param.name);
|
|
@@ -1330,16 +2105,21 @@ function collectFormSpecReferences(methods) {
|
|
|
1330
2105
|
}
|
|
1331
2106
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1332
2107
|
0 && (module.exports = {
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
2108
|
+
analyzeClassToIR,
|
|
2109
|
+
analyzeInterfaceToIR,
|
|
2110
|
+
analyzeTypeAliasToIR,
|
|
2111
|
+
canonicalizeChainDSL,
|
|
2112
|
+
canonicalizeTSDoc,
|
|
1336
2113
|
collectFormSpecReferences,
|
|
2114
|
+
createExtensionRegistry,
|
|
1337
2115
|
createProgramContext,
|
|
1338
2116
|
findClassByName,
|
|
1339
2117
|
findInterfaceByName,
|
|
1340
2118
|
findTypeAliasByName,
|
|
1341
2119
|
generateClassSchemas,
|
|
2120
|
+
generateJsonSchemaFromIR,
|
|
1342
2121
|
generateMethodSchemas,
|
|
1343
|
-
|
|
2122
|
+
generateUiSchemaFromIR,
|
|
2123
|
+
validateIR
|
|
1344
2124
|
});
|
|
1345
2125
|
//# sourceMappingURL=internals.cjs.map
|